mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-18 11:01:36 -05:00
Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b6f6844d | ||
|
|
e9ef95ef1f | ||
|
|
10eeb5374f | ||
|
|
332951bc8e | ||
|
|
4daf3280ff | ||
|
|
87bb2c74d9 | ||
|
|
ba011853a0 | ||
|
|
461a158ac3 | ||
|
|
85f59f1103 | ||
|
|
335b9f445f | ||
|
|
7cda45c904 | ||
|
|
4159fdc1a3 | ||
|
|
190da30979 | ||
|
|
ed9c74b900 | ||
|
|
6783766c33 | ||
|
|
b27018b379 | ||
|
|
021d07e04a | ||
|
|
bf0baec392 | ||
|
|
923cbac400 | ||
|
|
ab938f2536 | ||
|
|
a55b1804e9 | ||
|
|
4bfa6b3a5d | ||
|
|
d20f24be18 | ||
|
|
8a17bae7a6 | ||
|
|
d14c73fad5 | ||
|
|
ce878e1def | ||
|
|
a64a0d1db6 | ||
|
|
feab43f16d | ||
|
|
0d60fe775f | ||
|
|
6680be6a73 | ||
|
|
e026ab85a7 | ||
|
|
e53c903205 | ||
|
|
03dbe8565f | ||
|
|
708793cb23 | ||
|
|
43ae4fb0aa | ||
|
|
6b1d552277 | ||
|
|
f24f576b72 | ||
|
|
c23328564f | ||
|
|
cd6dd3dafa | ||
|
|
4081e15bef | ||
|
|
b05e3813d1 | ||
|
|
3048311f40 | ||
|
|
1045392d91 | ||
|
|
7b23ca8ee7 | ||
|
|
4d67eca8bb | ||
|
|
74c4b7311e | ||
|
|
713c74adfd | ||
|
|
408a8a6f19 | ||
|
|
e1485e49d3 | ||
|
|
30b66934cd | ||
|
|
fd49c081c2 | ||
|
|
1327120024 | ||
|
|
d2b5043972 | ||
|
|
a0e55ea3fd | ||
|
|
1f32de29c1 | ||
|
|
12be06d682 | ||
|
|
27ca9b13f8 | ||
|
|
be45905830 | ||
|
|
05d0a89655 | ||
|
|
3ba575dcd0 | ||
|
|
3e200b7f0f | ||
|
|
cbce1f7008 | ||
|
|
e99101447e | ||
|
|
0ddb326e44 | ||
|
|
460446a15c | ||
|
|
4eea24997f | ||
|
|
0b1e9c7c66 | ||
|
|
d51a7a9eb7 | ||
|
|
0f0b959e14 | ||
|
|
b2ceb09e4d | ||
|
|
53953f5cda | ||
|
|
fbd5e819a2 | ||
|
|
bdc391d376 | ||
|
|
6b618fb121 | ||
|
|
1f3e59c9f9 | ||
|
|
1956078c8c | ||
|
|
11230f59fc | ||
|
|
21bad7a01f | ||
|
|
6f9a27ecc7 | ||
|
|
c504113d13 | ||
|
|
c92ff60592 | ||
|
|
e9013d1a2a | ||
|
|
9c4580fe40 | ||
|
|
cb060cb5db | ||
|
|
6c3d85cc45 | ||
|
|
14ae89e87c | ||
|
|
24c48f025d | ||
|
|
f0a556f004 | ||
|
|
fd4d6abb4d | ||
|
|
41cc839380 | ||
|
|
c2a4380b96 | ||
|
|
6f402ac79f | ||
|
|
bf7c1306b1 | ||
|
|
c1509cf09d | ||
|
|
014bca031c | ||
|
|
4f864fd5bd | ||
|
|
2da67567e4 | ||
|
|
5b19d2b1fc | ||
|
|
a6837dcd40 | ||
|
|
af80751a1f | ||
|
|
dd02597c3b | ||
|
|
2926a3cbd8 | ||
|
|
b02bb3bfd4 | ||
|
|
67a4683bb1 | ||
|
|
9baee1c22c | ||
|
|
8cb67b4f9d | ||
|
|
0cd47bc328 | ||
|
|
07fb58d5e1 | ||
|
|
2d80d5e611 | ||
|
|
4bd63b615b | ||
|
|
335f667507 | ||
|
|
1819036d7d | ||
|
|
4f76f116ac | ||
|
|
1eba7c7d2a | ||
|
|
83234dd52c | ||
|
|
bae23b7fce | ||
|
|
3db61eaa82 | ||
|
|
5cf85a0361 | ||
|
|
ffe27f5bde | ||
|
|
78bcd9d54c | ||
|
|
1a9797f0ff | ||
|
|
0de94ff8a4 | ||
|
|
472233d9a7 | ||
|
|
f69e31b0d5 | ||
|
|
60b696cc31 | ||
|
|
549037f744 | ||
|
|
ca5e1e6133 | ||
|
|
3e3ff163db | ||
|
|
ca755365ce | ||
|
|
ea979de19f | ||
|
|
473ddfcdf1 | ||
|
|
a627285a4c | ||
|
|
322847469d | ||
|
|
6c5fac997f | ||
|
|
1871fd383e | ||
|
|
f5b147ca4b | ||
|
|
9d2b206156 | ||
|
|
a372c76e07 | ||
|
|
b1ce07d3ae | ||
|
|
bc09fb30d8 | ||
|
|
c13f219318 | ||
|
|
6b018c5d06 | ||
|
|
e69158d9b2 | ||
|
|
520cfb780a | ||
|
|
e686fed209 | ||
|
|
80ab19ac23 | ||
|
|
25228fa58e | ||
|
|
676d35090f | ||
|
|
e1a36a356c | ||
|
|
3274c0b734 | ||
|
|
9c95e1289f |
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -38,5 +38,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,5 +1,39 @@
|
||||
# Change Log
|
||||
|
||||
## Version 0.31.4
|
||||
* Release date: July 19, 2018
|
||||
* Release status: Public Preview
|
||||
|
||||
## What's new in this version
|
||||
* SQL Server Agent for SQL Operations Studio extension improvements
|
||||
* Added view of Alerts, Operators, and Proxies and icons on left pane
|
||||
* Added dialogs for New Job, New Job Step, New Alert, and New Operator
|
||||
* Added Delete Job, Delete Alert, and Delete Operator (right-click)
|
||||
* Added Previous Runs visualization
|
||||
* Added Filters for each column name
|
||||
* SQL Server Profiler for SQL Operations Studio extension improvements
|
||||
* Added Hotkeys to quickly launch and start/stop Profiler
|
||||
* Added 5 Default Templates to view Extended Events
|
||||
* Added Server/Database connection name
|
||||
* Added support for Azure SQL Database instances
|
||||
* Added suggestion to exit Profiler when tab is closed when Profiler is still running
|
||||
* Release of Combine Scripts Extension
|
||||
* Wizard and Dialog Extensibility
|
||||
* Fix GitHub Issues
|
||||
|
||||
## Version 0.30.6
|
||||
* Release date: June 20, 2018
|
||||
* Release status: Public Preview
|
||||
|
||||
## What's new in this version
|
||||
* **SQL Server Profiler for SQL Operations Studio *Preview*** extension initial release
|
||||
* The new **SQL Data Warehouse** extension includes rich customizable dashboard widgets surfacing insights to your data warehouse. This unlocks key scenarios around managing and tuning your data warehouse to ensure it is optimized for consistent performance.
|
||||
* **Edit Data "Filtering and Sorting"** support
|
||||
* **SQL Server Agent for SQL Operations Studio *Preview*** extension enhancements for Jobs and Job History views
|
||||
* Improved **Wizard & Dialog UI Builder Framework** extensibility APIs
|
||||
* Update VS Code Platform source code integrating [March 2018 (1.22)](https://code.visualstudio.com/updates/v1_22) and [April 2018 (1.23)](https://code.visualstudio.com/updates/v1_23) releases
|
||||
* Fix GitHub Issues
|
||||
|
||||
## Version 0.29.3
|
||||
* Release date: May 7, 2018
|
||||
* Release status: Public Preview
|
||||
@@ -9,7 +43,7 @@ The May release is focused on stabilization and bug fixes leading up to the Buil
|
||||
|
||||
* Announcing **Redgate SQL Search** extension available in Extension Manager
|
||||
* Community Localization available for 10 languages: **German, Spanish, French, Italian, Japanese, Korean, Portuguese, Russian, Simplified Chinese and Traditional Chinese!**
|
||||
* **GDPR-compliant** build has reduced telemetry collection, improved [opt-out](https://github.com/Microsoft/sqlopsstudio/wiki/How-to-Disable-Telemetry-Reporting) experience and in-product links to [Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement)
|
||||
* Reduced telemetry collection, improved [opt-out](https://github.com/Microsoft/sqlopsstudio/wiki/How-to-Disable-Telemetry-Reporting) experience and in-product links to [Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement)
|
||||
* Extension Manager has improved Marketplace experience to easily discover community extensions
|
||||
* SQL Agent extension Jobs and Job History view improvement
|
||||
* Updates for **whoisactive** and **Server Reports** extensions
|
||||
|
||||
17
README.md
17
README.md
@@ -4,16 +4,16 @@
|
||||
|
||||
SQL Operations 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 SQL Operations Studio May Public Preview**
|
||||
**Download SQL Operations Studio June Public Preview**
|
||||
|
||||
Platform | Link
|
||||
-- | --
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=873386
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=873387
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=873388
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=873389
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=873390
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=873391
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2005949
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2005950
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2005959
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2005960
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2006083
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2006084
|
||||
|
||||
Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions.
|
||||
|
||||
@@ -61,6 +61,7 @@ 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:
|
||||
|
||||
* lanceklinger `Fix for double clicking column handle in results table #1504`
|
||||
* westerncj for `Removed duplicate contribution from README.md (#753)`
|
||||
* ntovas for `Fix for duplicate extensions shown in "Save File" dialog. (#779)`
|
||||
* SebastianPfliegel for `Add cursor snippet (#475)`
|
||||
@@ -84,7 +85,7 @@ We would like to thank all our users who raised issues, and in particular the fo
|
||||
* Chinese (Traditional): Bruce Chen, Chiayi Yen, Kevin Yang, Winnie Lin, 保哥 Will, 謝政廷
|
||||
* Korean: Do-Kyun Kim, Evelyn Kim, Helen Jung, Hong Jmee, jeongwoo choi, Jun Hyoung Lee, Jungsun Kim정선, Justin Yoo, Kavrith mucha, Kiwoong Youm, MinGyu Ju, MVP_JUNO BEA, Sejun Kim, SOONMAN KWON, sung man ko, Yeongrak Choi, younggun kim, Youngjae Kim, 소영 이
|
||||
* Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov
|
||||
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
|
||||
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
|
||||
|
||||
|
||||
And of course we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/ThirdPartyNotices.txt)
|
||||
|
||||
@@ -61,6 +61,7 @@ Type: filesandordirs; Name: "{app}\_"
|
||||
|
||||
[Tasks]
|
||||
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
|
||||
Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked
|
||||
Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}"
|
||||
Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent
|
||||
|
||||
@@ -82,6 +83,13 @@ Root: HKCR; Subkey: "{#RegValueName}SourceFile\DefaultIcon"; ValueType: string;
|
||||
Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""
|
||||
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin'))
|
||||
|
||||
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
|
||||
|
||||
[Code]
|
||||
// Don't allow installing conflicting architectures
|
||||
function InitializeSetup(): Boolean;
|
||||
|
||||
@@ -1,38 +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 data from 'sqlops';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _apiWrapper: ApiWrapper;
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
|
||||
this._apiWrapper = apiWrapper || new ApiWrapper();
|
||||
this._context = context;
|
||||
|
||||
console.log('Got: ' + apiWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
|
||||
this._apiWrapper.registerWebviewProvider('data-management-agent', webview => {
|
||||
webview.html = '<div><h1>SQL Agent</h1></div>';
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
{
|
||||
"name": "agent",
|
||||
"displayName": "SQL Server Agent",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs (early preview)",
|
||||
"version": "0.29.0",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||
"version": "0.31.4",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
"icon": "images/sqlserver.png",
|
||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||
"engines": {
|
||||
"vscode": "0.10.x"
|
||||
"vscode": "0.10.x"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./client/out/main",
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:agent-client"
|
||||
},
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/sqlopsstudio.git"
|
||||
@@ -29,22 +26,32 @@
|
||||
"outputChannels": [
|
||||
"sqlagent"
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "agent.openNewStepDialog",
|
||||
"title": "agent.openNewStepDialog"
|
||||
}
|
||||
],
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "data-management-agent",
|
||||
"description": "Manage and troubleshoot SQL Agent jobs",
|
||||
"provider": "MSSQL",
|
||||
"title": "SQL Agent",
|
||||
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
|
||||
"container": {
|
||||
"controlhost-container": {
|
||||
"type": "agent"
|
||||
}
|
||||
}
|
||||
{
|
||||
"id": "data-management-agent",
|
||||
"description": "Manage and troubleshoot SQL Agent jobs",
|
||||
"provider": "MSSQL",
|
||||
"title": "SQL Agent",
|
||||
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
|
||||
"container": {
|
||||
"controlhost-container": {
|
||||
"type": "agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vscode": "1.0.1"
|
||||
}
|
||||
"mocha-junit-reporter": "^1.17.0",
|
||||
"mocha-multi-reporters": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
||||
38
extensions/agent/src/agentUtils.ts
Normal file
38
extensions/agent/src/agentUtils.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export class AgentUtils {
|
||||
|
||||
private static _agentService: sqlops.AgentServicesProvider;
|
||||
private static _connectionService: sqlops.ConnectionProvider;
|
||||
private static _queryProvider: sqlops.QueryProvider;
|
||||
|
||||
public static async getAgentService(): Promise<sqlops.AgentServicesProvider> {
|
||||
if (!AgentUtils._agentService) {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
this._agentService = sqlops.dataprotocol.getProvider<sqlops.AgentServicesProvider>(currentConnection.providerName, sqlops.DataProviderType.AgentServicesProvider);
|
||||
}
|
||||
return AgentUtils._agentService;
|
||||
}
|
||||
|
||||
public static async getDatabases(ownerUri: string): Promise<string[]> {
|
||||
if (!AgentUtils._connectionService) {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
this._connectionService = sqlops.dataprotocol.getProvider<sqlops.ConnectionProvider>(currentConnection.providerName, sqlops.DataProviderType.ConnectionProvider);
|
||||
}
|
||||
return AgentUtils._connectionService.listDatabases(ownerUri).then(result => {
|
||||
if (result && result.databaseNames && result.databaseNames.length > 0) {
|
||||
return result.databaseNames;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static async getQueryProvider(): Promise<sqlops.QueryProvider> {
|
||||
if (!AgentUtils._queryProvider) {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
this._queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(currentConnection.providerName, sqlops.DataProviderType.QueryProvider);
|
||||
}
|
||||
return this._queryProvider;
|
||||
}
|
||||
}
|
||||
132
extensions/agent/src/data/alertData.ts
Normal file
132
extensions/agent/src/data/alertData.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AlertData implements IAgentDialogData {
|
||||
public static readonly AlertTypeSqlServerEventString: string = localize('alertData.DefaultAlertTypString', 'SQL Server event alert');
|
||||
public static readonly AlertTypePerformanceConditionString: string = localize('alertDialog.PerformanceCondition', 'SQL Server performance condition alert');
|
||||
public static readonly AlertTypeWmiEventString: string = localize('alertDialog.WmiEvent', 'WMI event alert');
|
||||
public static readonly DefaultAlertTypeString: string = AlertData.AlertTypeSqlServerEventString;
|
||||
|
||||
ownerUri: string;
|
||||
dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
id: number;
|
||||
name: string;
|
||||
originalName: string;
|
||||
delayBetweenResponses: number;
|
||||
eventDescriptionKeyword: string;
|
||||
eventSource: string;
|
||||
hasNotification: number;
|
||||
includeEventDescription: string;
|
||||
isEnabled: boolean = true;
|
||||
jobId: string;
|
||||
jobName: string;
|
||||
lastOccurrenceDate: string;
|
||||
lastResponseDate: string;
|
||||
messageId: number;
|
||||
notificationMessage: string;
|
||||
occurrenceCount: number;
|
||||
performanceCondition: string;
|
||||
severity: number;
|
||||
databaseName: string;
|
||||
countResetDate: string;
|
||||
categoryName: string;
|
||||
alertType: string = AlertData.DefaultAlertTypeString;
|
||||
wmiEventNamespace: string;
|
||||
wmiEventQuery: string;
|
||||
|
||||
constructor(ownerUri:string, alertInfo: sqlops.AgentAlertInfo) {
|
||||
this.ownerUri = ownerUri;
|
||||
|
||||
if (alertInfo) {
|
||||
this.dialogMode = AgentDialogMode.EDIT;
|
||||
this.id = alertInfo.id;
|
||||
this.name = alertInfo.name;
|
||||
this.originalName = alertInfo.name;
|
||||
this.delayBetweenResponses = alertInfo.delayBetweenResponses;
|
||||
this.eventDescriptionKeyword = alertInfo.eventDescriptionKeyword;
|
||||
this.eventSource = alertInfo.eventSource;
|
||||
this.hasNotification = alertInfo.hasNotification;
|
||||
this.includeEventDescription = alertInfo.includeEventDescription.toString();
|
||||
this.isEnabled = alertInfo.isEnabled;
|
||||
this.jobId = alertInfo.jobId;
|
||||
this.jobName = alertInfo.jobName;
|
||||
this.lastOccurrenceDate = alertInfo.lastOccurrenceDate;
|
||||
this.lastResponseDate = alertInfo.lastResponseDate;
|
||||
this.messageId = alertInfo.messageId;
|
||||
this.notificationMessage = alertInfo.notificationMessage;
|
||||
this.occurrenceCount = alertInfo.occurrenceCount;
|
||||
this.performanceCondition = alertInfo.performanceCondition;
|
||||
this.severity = alertInfo.severity;
|
||||
this.databaseName = alertInfo.databaseName;
|
||||
this.countResetDate = alertInfo.countResetDate;
|
||||
this.categoryName = alertInfo.categoryName;
|
||||
this.alertType = alertInfo.alertType.toString();
|
||||
this.wmiEventNamespace = alertInfo.wmiEventNamespace;
|
||||
this.wmiEventQuery = alertInfo.wmiEventQuery;
|
||||
}
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = this.dialogMode === AgentDialogMode.CREATE
|
||||
? await agentService.createAlert(this.ownerUri, this.toAgentAlertInfo())
|
||||
: await agentService.updateAlert(this.ownerUri, this.originalName, this.toAgentAlertInfo());
|
||||
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('alertData.saveErrorMessage', "Alert update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
public toAgentAlertInfo(): sqlops.AgentAlertInfo {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
delayBetweenResponses: this.delayBetweenResponses,
|
||||
eventDescriptionKeyword: this.eventDescriptionKeyword,
|
||||
eventSource: this.eventSource,
|
||||
hasNotification: this.hasNotification,
|
||||
includeEventDescription: sqlops.NotifyMethods.none, // this.includeEventDescription,
|
||||
isEnabled: this.isEnabled,
|
||||
jobId: this.jobId,
|
||||
jobName: this.jobName,
|
||||
lastOccurrenceDate: this.lastOccurrenceDate,
|
||||
lastResponseDate: this.lastResponseDate,
|
||||
messageId: this.messageId,
|
||||
notificationMessage: this.notificationMessage,
|
||||
occurrenceCount: this.occurrenceCount,
|
||||
performanceCondition: this.performanceCondition,
|
||||
severity: this.severity,
|
||||
databaseName: this.databaseName,
|
||||
countResetDate: this.countResetDate,
|
||||
categoryName: this.categoryName,
|
||||
alertType: AlertData.getAlertTypeFromString(this.alertType),
|
||||
wmiEventNamespace: this.wmiEventNamespace,
|
||||
wmiEventQuery: this.wmiEventQuery
|
||||
};
|
||||
}
|
||||
|
||||
private static getAlertTypeFromString(alertTypeString: string): sqlops.AlertType {
|
||||
if (alertTypeString === AlertData.AlertTypePerformanceConditionString) {
|
||||
return sqlops.AlertType.sqlServerPerformanceCondition;
|
||||
} else if (alertTypeString === AlertData.AlertTypeWmiEventString) {
|
||||
return sqlops.AlertType.wmiEvent;
|
||||
} else {
|
||||
return sqlops.AlertType.sqlServerEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
177
extensions/agent/src/data/jobData.ts
Normal file
177
extensions/agent/src/data/jobData.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class JobData implements IAgentDialogData {
|
||||
|
||||
private readonly JobCompletionActionCondition_Always: string = localize('jobData.whenJobCompletes', 'When the job completes');
|
||||
private readonly JobCompletionActionCondition_OnFailure: string = localize('jobData.whenJobFails', 'When the job fails');
|
||||
private readonly JobCompletionActionCondition_OnSuccess: string = localize('jobData.whenJobSucceeds', 'When the job succeeds');
|
||||
|
||||
// Error Messages
|
||||
private readonly CreateJobErrorMessage_NameIsEmpty = localize('jobData.jobNameRequired', 'Job name must be provided');
|
||||
|
||||
private _ownerUri: string;
|
||||
private _jobCategories: string[];
|
||||
private _operators: string[];
|
||||
private _defaultOwner: string;
|
||||
private _jobCompletionActionConditions: sqlops.CategoryValue[];
|
||||
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
public name: string;
|
||||
public originalName: string;
|
||||
public enabled: boolean = true;
|
||||
public description: string;
|
||||
public category: string;
|
||||
public categoryId: number;
|
||||
public owner: string;
|
||||
public emailLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
|
||||
public pageLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
|
||||
public eventLogLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
|
||||
public deleteLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnSuccess;
|
||||
public operatorToEmail: string;
|
||||
public operatorToPage: string;
|
||||
public jobSteps: sqlops.AgentJobStepInfo[];
|
||||
public jobSchedules: sqlops.AgentJobScheduleInfo[];
|
||||
public alerts: sqlops.AgentAlertInfo[];
|
||||
|
||||
constructor(
|
||||
ownerUri: string,
|
||||
jobInfo: sqlops.AgentJobInfo = undefined,
|
||||
private _agentService: sqlops.AgentServicesProvider = undefined) {
|
||||
|
||||
this._ownerUri = ownerUri;
|
||||
if (jobInfo) {
|
||||
this.dialogMode = AgentDialogMode.EDIT;
|
||||
this.name = jobInfo.name;
|
||||
this.originalName = jobInfo.name;
|
||||
this.owner = jobInfo.owner;
|
||||
this.category = jobInfo.category;
|
||||
this.description = jobInfo.description;
|
||||
this.enabled = jobInfo.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public get jobCategories(): string[] {
|
||||
return this._jobCategories;
|
||||
}
|
||||
|
||||
public get operators(): string[] {
|
||||
return this._operators;
|
||||
}
|
||||
|
||||
public get ownerUri(): string {
|
||||
return this._ownerUri;
|
||||
}
|
||||
|
||||
public get defaultOwner(): string {
|
||||
return this._defaultOwner;
|
||||
}
|
||||
|
||||
public get JobCompletionActionConditions(): sqlops.CategoryValue[] {
|
||||
return this._jobCompletionActionConditions;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
this._agentService = await AgentUtils.getAgentService();
|
||||
let jobDefaults = await this._agentService.getJobDefaults(this.ownerUri);
|
||||
if (jobDefaults && jobDefaults.success) {
|
||||
this._jobCategories = jobDefaults.categories.map((cat) => {
|
||||
return cat.name;
|
||||
});
|
||||
|
||||
this._defaultOwner = jobDefaults.owner;
|
||||
|
||||
this._operators = ['', this._defaultOwner];
|
||||
}
|
||||
|
||||
this._jobCompletionActionConditions = [{
|
||||
displayName: this.JobCompletionActionCondition_OnSuccess,
|
||||
name: sqlops.JobCompletionActionCondition.OnSuccess.toString()
|
||||
}, {
|
||||
displayName: this.JobCompletionActionCondition_OnFailure,
|
||||
name: sqlops.JobCompletionActionCondition.OnFailure.toString()
|
||||
}, {
|
||||
displayName: this.JobCompletionActionCondition_Always,
|
||||
name: sqlops.JobCompletionActionCondition.Always.toString()
|
||||
}];
|
||||
|
||||
this.jobSchedules = [];
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let jobInfo: sqlops.AgentJobInfo = this.toAgentJobInfo();
|
||||
let result = this.dialogMode === AgentDialogMode.CREATE
|
||||
? await this._agentService.createJob(this.ownerUri, jobInfo)
|
||||
: await this._agentService.updateJob(this.ownerUri, this.originalName, jobInfo);
|
||||
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('jobData.saveErrorMessage', "Job update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
public validate(): { valid: boolean, errorMessages: string[] } {
|
||||
let validationErrors: string[] = [];
|
||||
|
||||
if (!(this.name && this.name.trim())) {
|
||||
validationErrors.push(this.CreateJobErrorMessage_NameIsEmpty);
|
||||
}
|
||||
|
||||
return {
|
||||
valid: validationErrors.length === 0,
|
||||
errorMessages: validationErrors
|
||||
};
|
||||
}
|
||||
|
||||
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
|
||||
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
|
||||
if (!existingSchedule) {
|
||||
this.jobSchedules.push(schedule);
|
||||
}
|
||||
}
|
||||
|
||||
public toAgentJobInfo(): sqlops.AgentJobInfo {
|
||||
return {
|
||||
name: this.name,
|
||||
owner: this.owner,
|
||||
description: this.description,
|
||||
EmailLevel: this.emailLevel,
|
||||
PageLevel: this.pageLevel,
|
||||
EventLogLevel: this.eventLogLevel,
|
||||
DeleteLevel: this.deleteLevel,
|
||||
OperatorToEmail: this.operatorToEmail,
|
||||
OperatorToPage: this.operatorToPage,
|
||||
enabled: this.enabled,
|
||||
category: this.category,
|
||||
Alerts: this.alerts,
|
||||
JobSchedules: this.jobSchedules,
|
||||
JobSteps: this.jobSteps,
|
||||
// The properties below are not collected from UI
|
||||
// We could consider using a seperate class for create job request
|
||||
//
|
||||
currentExecutionStatus: 0,
|
||||
lastRunOutcome: 0,
|
||||
currentExecutionStep: '',
|
||||
hasTarget: true,
|
||||
hasSchedule: false,
|
||||
hasStep: false,
|
||||
runnable: true,
|
||||
categoryId: 0,
|
||||
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
|
||||
lastRun: '',
|
||||
nextRun: '',
|
||||
jobId: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
76
extensions/agent/src/data/jobStepData.ts
Normal file
76
extensions/agent/src/data/jobStepData.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class JobStepData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
public ownerUri: string;
|
||||
public jobId: string; //
|
||||
public jobName: string;
|
||||
public script: string; //
|
||||
public scriptName: string;
|
||||
public stepName: string; //
|
||||
public subSystem: string; //
|
||||
public id: number;
|
||||
public failureAction: string; //
|
||||
public successAction: string; //
|
||||
public failStepId: number;
|
||||
public successStepId: number;
|
||||
public command: string;
|
||||
public commandExecutionSuccessCode: number;
|
||||
public databaseName: string; //
|
||||
public databaseUserName: string;
|
||||
public server: string;
|
||||
public outputFileName: string; //
|
||||
public appendToLogFile: boolean;
|
||||
public appendToStepHist: boolean;
|
||||
public writeLogToTable: boolean;
|
||||
public appendLogToTable: boolean;
|
||||
public retryAttempts: number; //
|
||||
public retryInterval: number; //
|
||||
public proxyName: string;
|
||||
|
||||
constructor(ownerUri:string) {
|
||||
this.ownerUri = ownerUri;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
agentService.createJobStep(this.ownerUri, {
|
||||
jobId: this.jobId,
|
||||
jobName: this.jobName,
|
||||
script: this.script,
|
||||
scriptName: this.scriptName,
|
||||
stepName: this.stepName,
|
||||
subSystem: this.subSystem,
|
||||
id: 1,
|
||||
failureAction: this.failureAction,
|
||||
successAction: this.successAction,
|
||||
failStepId: this.failStepId,
|
||||
successStepId: this.successStepId,
|
||||
command: this.command,
|
||||
commandExecutionSuccessCode: this.commandExecutionSuccessCode,
|
||||
databaseName: this.databaseName,
|
||||
databaseUserName: this.databaseUserName,
|
||||
server: this.server,
|
||||
outputFileName: this.outputFileName,
|
||||
appendToLogFile: this.appendToLogFile,
|
||||
appendToStepHist: this.appendToStepHist,
|
||||
writeLogToTable: this.writeLogToTable,
|
||||
appendLogToTable: this.appendLogToTable,
|
||||
retryAttempts: this.retryAttempts,
|
||||
retryInterval: this.retryInterval,
|
||||
proxyName: this.proxyName
|
||||
}).then(result => {
|
||||
console.info(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
74
extensions/agent/src/data/operatorData.ts
Normal file
74
extensions/agent/src/data/operatorData.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class OperatorData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
ownerUri: string;
|
||||
name: string;
|
||||
id: number;
|
||||
emailAddress: string;
|
||||
enabled: boolean;
|
||||
lastEmailDate: string;
|
||||
lastNetSendDate: string;
|
||||
lastPagerDate: string;
|
||||
pagerAddress: string;
|
||||
categoryName: string;
|
||||
pagerDays: string;
|
||||
saturdayPagerEndTime: string;
|
||||
saturdayPagerStartTime: string;
|
||||
sundayPagerEndTime: string;
|
||||
sundayPagerStartTime: string;
|
||||
netSendAddress: string;
|
||||
weekdayPagerStartTime: string;
|
||||
weekdayPagerEndTime: string;
|
||||
|
||||
constructor(ownerUri:string, operatorInfo: sqlops.AgentOperatorInfo) {
|
||||
this.ownerUri = ownerUri;
|
||||
|
||||
if (operatorInfo) {
|
||||
this.dialogMode = AgentDialogMode.EDIT;
|
||||
this.name = operatorInfo.name;
|
||||
this.enabled = operatorInfo.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = await agentService.createOperator(this.ownerUri, this.toAgentOperatorInfo());
|
||||
if (!result || !result.success) {
|
||||
// TODO handle error here
|
||||
}
|
||||
}
|
||||
|
||||
public toAgentOperatorInfo(): sqlops.AgentOperatorInfo {
|
||||
return {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
emailAddress: this.emailAddress,
|
||||
enabled: this.enabled,
|
||||
lastEmailDate: this.lastEmailDate,
|
||||
lastNetSendDate: this.lastNetSendDate,
|
||||
lastPagerDate: this.lastPagerDate,
|
||||
pagerAddress: this.pagerAddress,
|
||||
categoryName: this.categoryName,
|
||||
pagerDays: sqlops.WeekDays.weekDays, //this.pagerDays,
|
||||
saturdayPagerEndTime: this.saturdayPagerEndTime,
|
||||
saturdayPagerStartTime: this.saturdayPagerStartTime,
|
||||
sundayPagerEndTime: this.sundayPagerEndTime,
|
||||
sundayPagerStartTime: this.sundayPagerStartTime,
|
||||
netSendAddress: this.netSendAddress,
|
||||
weekdayPagerStartTime: this.weekdayPagerStartTime,
|
||||
weekdayPagerEndTime: this.weekdayPagerEndTime
|
||||
};
|
||||
}
|
||||
}
|
||||
31
extensions/agent/src/data/pickScheduleData.ts
Normal file
31
extensions/agent/src/data/pickScheduleData.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class PickScheduleData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.VIEW;
|
||||
public ownerUri: string;
|
||||
public schedules: sqlops.AgentJobScheduleInfo[];
|
||||
public selectedSchedule: sqlops.AgentJobScheduleInfo;
|
||||
|
||||
constructor(ownerUri:string) {
|
||||
this.ownerUri = ownerUri;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = await agentService.getJobSchedules(this.ownerUri);
|
||||
if (result && result.success) {
|
||||
this.schedules = result.schedules;
|
||||
}
|
||||
}
|
||||
|
||||
public async save() {
|
||||
}
|
||||
}
|
||||
54
extensions/agent/src/data/proxyData.ts
Normal file
54
extensions/agent/src/data/proxyData.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class ProxyData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
ownerUri: string;
|
||||
id: number;
|
||||
accountName: string;
|
||||
description: string;
|
||||
credentialName: string;
|
||||
credentialIdentity: string;
|
||||
credentialId: number;
|
||||
isEnabled: boolean;
|
||||
|
||||
constructor(ownerUri:string, proxyInfo: sqlops.AgentProxyInfo) {
|
||||
this.ownerUri = ownerUri;
|
||||
|
||||
if (proxyInfo) {
|
||||
this.accountName = proxyInfo.accountName;
|
||||
this.credentialName = proxyInfo.credentialName;
|
||||
this.description = proxyInfo.description;
|
||||
}
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = await agentService.createProxy(this.ownerUri, this.toAgentProxyInfo());
|
||||
if (!result || !result.success) {
|
||||
// TODO handle error here
|
||||
}
|
||||
}
|
||||
|
||||
public toAgentProxyInfo(): sqlops.AgentProxyInfo {
|
||||
return {
|
||||
id: this.id,
|
||||
accountName: this.accountName,
|
||||
description: this.description,
|
||||
credentialName: this.credentialName,
|
||||
credentialIdentity: this.credentialIdentity,
|
||||
credentialId: this.credentialId,
|
||||
isEnabled: this.isEnabled
|
||||
};
|
||||
}
|
||||
}
|
||||
31
extensions/agent/src/data/scheduleData.ts
Normal file
31
extensions/agent/src/data/scheduleData.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class ScheduleData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
public ownerUri: string;
|
||||
public schedules: sqlops.AgentJobScheduleInfo[];
|
||||
public selectedSchedule: sqlops.AgentJobScheduleInfo;
|
||||
|
||||
constructor(ownerUri:string) {
|
||||
this.ownerUri = ownerUri;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = await agentService.getJobSchedules(this.ownerUri);
|
||||
if (result && result.success) {
|
||||
this.schedules = result.schedules;
|
||||
}
|
||||
}
|
||||
|
||||
public async save() {
|
||||
}
|
||||
}
|
||||
78
extensions/agent/src/dialogs/agentDialog.ts
Normal file
78
extensions/agent/src/dialogs/agentDialog.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export abstract class AgentDialog<T extends IAgentDialogData> {
|
||||
|
||||
private static readonly OkButtonText: string = localize('agentDialog.OK', 'OK');
|
||||
private static readonly CancelButtonText: string = localize('agentDialog.Cancel', 'Cancel');
|
||||
|
||||
protected _onSuccess: vscode.EventEmitter<T> = new vscode.EventEmitter<T>();
|
||||
public readonly onSuccess: vscode.Event<T> = this._onSuccess.event;
|
||||
public dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
|
||||
constructor(public ownerUri: string, public model: T, public title: string) {
|
||||
}
|
||||
|
||||
public get dialogMode(): AgentDialogMode {
|
||||
return this.model.dialogMode;
|
||||
}
|
||||
|
||||
protected abstract async updateModel();
|
||||
|
||||
protected abstract async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog);
|
||||
|
||||
public async openDialog() {
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.title);
|
||||
|
||||
await this.model.initialize();
|
||||
|
||||
await this.initializeDialog(this.dialog);
|
||||
|
||||
this.dialog.okButton.label = AgentDialog.OkButtonText;
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
|
||||
this.dialog.cancelButton.label = AgentDialog.CancelButtonText;
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
protected async execute() {
|
||||
this.updateModel();
|
||||
let success = await this.model.save();
|
||||
if (success) {
|
||||
this._onSuccess.fire(this.model);
|
||||
}
|
||||
}
|
||||
|
||||
protected async cancel() {
|
||||
}
|
||||
|
||||
protected getActualConditionValue(checkbox: sqlops.CheckBoxComponent, dropdown: sqlops.DropDownComponent): sqlops.JobCompletionActionCondition {
|
||||
return checkbox.checked ? Number(this.getDropdownValue(dropdown)) : sqlops.JobCompletionActionCondition.Never;
|
||||
}
|
||||
|
||||
protected getDropdownValue(dropdown: sqlops.DropDownComponent): string {
|
||||
return (typeof dropdown.value === 'string') ? dropdown.value : dropdown.value.name;
|
||||
}
|
||||
|
||||
protected setConditionDropdownSelectedValue(dropdown: sqlops.DropDownComponent, selectedValue: number) {
|
||||
let idx: number = 0;
|
||||
for (idx = 0; idx < dropdown.values.length; idx++) {
|
||||
if (Number((<sqlops.CategoryValue>dropdown.values[idx]).name) === selectedValue) {
|
||||
dropdown.value = dropdown.values[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
528
extensions/agent/src/dialogs/alertDialog.ts
Normal file
528
extensions/agent/src/dialogs/alertDialog.ts
Normal file
@@ -0,0 +1,528 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { AlertData } from '../data/alertData';
|
||||
import { OperatorDialog } from './operatorDialog';
|
||||
import { JobDialog } from './jobDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AlertDialog extends AgentDialog<AlertData> {
|
||||
|
||||
// Top level
|
||||
private static readonly CreateDialogTitle: string = localize('alertDialog.createAlert', 'Create Alert');
|
||||
private static readonly EditDialogTitle: string = localize('alertDialog.editAlert', 'Edit Alert');
|
||||
private static readonly GeneralTabText: string = localize('alertDialog.General', 'General');
|
||||
private static readonly ResponseTabText: string = localize('alertDialog.Response', 'Response');
|
||||
private static readonly OptionsTabText: string = localize('alertDialog.Options', 'Options');
|
||||
private static readonly EventAlertText: string = localize('alertDialog.eventAlert', 'Event alert definition');
|
||||
|
||||
// General tab strings
|
||||
private static readonly NameLabel: string = localize('alertDialog.Name', 'Name');
|
||||
private static readonly TypeLabel: string = localize('alertDialog.Type', 'Type');
|
||||
private static readonly EnabledCheckboxLabel: string = localize('alertDialog.Enabled', 'Enabled');
|
||||
private static readonly DatabaseLabel: string = localize('alertDialog.DatabaseName', 'Database name');
|
||||
private static readonly ErrorNumberLabel: string = localize('alertDialog.ErrorNumber', 'Error number');
|
||||
private static readonly SeverityLabel: string = localize('alertDialog.Severity', 'Severity');
|
||||
private static readonly RaiseIfMessageContainsLabel: string = localize('alertDialog.RaiseAlertContains', 'Raise alert when message contains');
|
||||
private static readonly MessageTextLabel: string = localize('alertDialog.MessageText', 'Message text');
|
||||
private static readonly AlertSeverity001Label: string = localize('alertDialog.Severity001', '001 - Miscellaneous System Information');
|
||||
private static readonly AlertSeverity002Label: string = localize('alertDialog.Severity002', '002 - Reserved');
|
||||
private static readonly AlertSeverity003Label: string = localize('alertDialog.Severity003', '003 - Reserved');
|
||||
private static readonly AlertSeverity004Label: string = localize('alertDialog.Severity004', '004 - Reserved');
|
||||
private static readonly AlertSeverity005Label: string = localize('alertDialog.Severity005', '005 - Reserved');
|
||||
private static readonly AlertSeverity006Label: string = localize('alertDialog.Severity006', '006 - Reserved');
|
||||
private static readonly AlertSeverity007Label: string = localize('alertDialog.Severity007', '007 - Notification: Status Information');
|
||||
private static readonly AlertSeverity008Label: string = localize('alertDialog.Severity008', '008 - Notification: User Intervention Required');
|
||||
private static readonly AlertSeverity009Label: string = localize('alertDialog.Severity009', '009 - User Defined');
|
||||
private static readonly AlertSeverity010Label: string = localize('alertDialog.Severity010', '010 - Information');
|
||||
private static readonly AlertSeverity011Label: string = localize('alertDialog.Severity011', '011 - Specified Database Object Not Found');
|
||||
private static readonly AlertSeverity012Label: string = localize('alertDialog.Severity012', '012 - Unused');
|
||||
private static readonly AlertSeverity013Label: string = localize('alertDialog.Severity013', '013 - User Transaction Syntax Error');
|
||||
private static readonly AlertSeverity014Label: string = localize('alertDialog.Severity014', '014 - Insufficient Permission');
|
||||
private static readonly AlertSeverity015Label: string = localize('alertDialog.Severity015', '015 - Syntax Error in SQL Statements');
|
||||
private static readonly AlertSeverity016Label: string = localize('alertDialog.Severity016', '016 - Miscellaneous User Error');
|
||||
private static readonly AlertSeverity017Label: string = localize('alertDialog.Severity017', '017 - Insufficient Resources');
|
||||
private static readonly AlertSeverity018Label: string = localize('alertDialog.Severity018', '018 - Nonfatal Internal Error');
|
||||
private static readonly AlertSeverity019Label: string = localize('alertDialog.Severity019', '019 - Fatal Error in Resource');
|
||||
private static readonly AlertSeverity020Label: string = localize('alertDialog.Severity020', '020 - Fatal Error in Current Process');
|
||||
private static readonly AlertSeverity021Label: string = localize('alertDialog.Severity021', '021 - Fatal Error in Database Processes');
|
||||
private static readonly AlertSeverity022Label: string = localize('alertDialog.Severity022', '022 - Fatal Error: Table Integrity Suspect');
|
||||
private static readonly AlertSeverity023Label: string = localize('alertDialog.Severity023', '023 - Fatal Error: Database Integrity Suspect');
|
||||
private static readonly AlertSeverity024Label: string = localize('alertDialog.Severity024', '024 - Fatal Error: Hardware Error');
|
||||
private static readonly AlertSeverity025Label: string = localize('alertDialog.Severity025', '025 - Fatal Error');
|
||||
private static readonly AllDatabases: string = localize('alertDialog.AllDatabases', '<all databases>');
|
||||
|
||||
private static readonly AlertTypes: string[] = [
|
||||
AlertData.AlertTypeSqlServerEventString,
|
||||
// Disabled until next release
|
||||
// AlertData.AlertTypePerformanceConditionString,
|
||||
// AlertData.AlertTypeWmiEventString
|
||||
];
|
||||
|
||||
private static readonly AlertSeverities: string[] = [
|
||||
AlertDialog.AlertSeverity001Label,
|
||||
AlertDialog.AlertSeverity002Label,
|
||||
AlertDialog.AlertSeverity003Label,
|
||||
AlertDialog.AlertSeverity004Label,
|
||||
AlertDialog.AlertSeverity005Label,
|
||||
AlertDialog.AlertSeverity006Label,
|
||||
AlertDialog.AlertSeverity007Label,
|
||||
AlertDialog.AlertSeverity008Label,
|
||||
AlertDialog.AlertSeverity009Label,
|
||||
AlertDialog.AlertSeverity010Label,
|
||||
AlertDialog.AlertSeverity011Label,
|
||||
AlertDialog.AlertSeverity012Label,
|
||||
AlertDialog.AlertSeverity013Label,
|
||||
AlertDialog.AlertSeverity014Label,
|
||||
AlertDialog.AlertSeverity015Label,
|
||||
AlertDialog.AlertSeverity016Label,
|
||||
AlertDialog.AlertSeverity017Label,
|
||||
AlertDialog.AlertSeverity018Label,
|
||||
AlertDialog.AlertSeverity019Label,
|
||||
AlertDialog.AlertSeverity020Label,
|
||||
AlertDialog.AlertSeverity021Label,
|
||||
AlertDialog.AlertSeverity022Label,
|
||||
AlertDialog.AlertSeverity023Label,
|
||||
AlertDialog.AlertSeverity024Label,
|
||||
AlertDialog.AlertSeverity025Label
|
||||
];
|
||||
|
||||
// Response tab strings
|
||||
private static readonly ExecuteJobCheckBoxLabel: string = localize('alertDialog.ExecuteJob', 'Execute Job');
|
||||
private static readonly ExecuteJobTextBoxLabel: string = localize('alertDialog.ExecuteJobName', 'Job Name');
|
||||
private static readonly NotifyOperatorsTextBoxLabel: string = localize('alertDialog.NotifyOperators', 'Notify Operators');
|
||||
private static readonly NewJobButtonLabel: string = localize('alertDialog.NewJob', 'New Job');
|
||||
private static readonly OperatorListLabel: string = localize('alertDialog.OperatorList', 'Operator List');
|
||||
private static readonly OperatorNameColumnLabel: string = localize('alertDialog.OperatorName', 'Operator');
|
||||
private static readonly OperatorEmailColumnLabel: string = localize('alertDialog.OperatorEmail', 'E-mail');
|
||||
private static readonly OperatorPagerColumnLabel: string = localize('alertDialog.OperatorPager', 'Pager');
|
||||
private static readonly NewOperatorButtonLabel: string = localize('alertDialog.NewOperator', 'New Operator');
|
||||
|
||||
// Options tab strings
|
||||
private static readonly IncludeErrorInEmailCheckBoxLabel: string = localize('alertDialog.IncludeErrorInEmail', 'Include alert error text in e-mail');
|
||||
private static readonly IncludeErrorInPagerCheckBoxLabel: string = localize('alertDialog.IncludeErrorInPager', 'Include alert error text in pager');
|
||||
private static readonly AdditionalMessageTextBoxLabel: string = localize('alertDialog.AdditionalNotification', 'Additional notification message to send');
|
||||
private static readonly DelayBetweenResponsesTextBoxLabel: string = localize('alertDialog.DelayBetweenResponse', 'Delay between responses');
|
||||
private static readonly DelayMinutesTextBoxLabel: string = localize('alertDialog.DelayMinutes', 'Delay Minutes');
|
||||
private static readonly DelaySecondsTextBoxLabel: string = localize('alertDialog.DelaySeconds', 'Delay Seconds');
|
||||
|
||||
// UI Components
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private responseTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private optionsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
// General tab controls
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private typeDropDown: sqlops.DropDownComponent;
|
||||
private severityDropDown: sqlops.DropDownComponent;
|
||||
private databaseDropDown: sqlops.DropDownComponent;
|
||||
private enabledCheckBox: sqlops.CheckBoxComponent;
|
||||
private errorNumberRadioButton: sqlops.RadioButtonComponent;
|
||||
private severityRadioButton: sqlops.RadioButtonComponent;
|
||||
private errorNumberTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
private raiseAlertMessageCheckBox: sqlops.CheckBoxComponent;
|
||||
private raiseAlertMessageTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
// Response tab controls
|
||||
private executeJobTextBox: sqlops.InputBoxComponent;
|
||||
private executeJobCheckBox: sqlops.CheckBoxComponent;
|
||||
private newJobButton: sqlops.ButtonComponent;
|
||||
private notifyOperatorsCheckBox: sqlops.CheckBoxComponent;
|
||||
private operatorsTable: sqlops.TableComponent;
|
||||
private newOperatorButton: sqlops.ButtonComponent;
|
||||
|
||||
// Options tab controls
|
||||
private additionalMessageTextBox: sqlops.InputBoxComponent;
|
||||
private includeErrorInEmailTextBox: sqlops.CheckBoxComponent;
|
||||
private includeErrorInPagerTextBox: sqlops.CheckBoxComponent;
|
||||
private delayMinutesTextBox: sqlops.InputBoxComponent;
|
||||
private delaySecondsTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
private jobs: string[];
|
||||
private databases: string[];
|
||||
|
||||
constructor(ownerUri: string, alertInfo: sqlops.AgentAlertInfo = undefined, jobs: string[]) {
|
||||
super(ownerUri,
|
||||
new AlertData(ownerUri, alertInfo),
|
||||
alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle);
|
||||
this.jobs = jobs;
|
||||
}
|
||||
|
||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
this.databases = await AgentUtils.getDatabases(this.ownerUri);
|
||||
this.databases.unshift(AlertDialog.AllDatabases);
|
||||
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(AlertDialog.GeneralTabText);
|
||||
this.responseTab = sqlops.window.modelviewdialog.createTab(AlertDialog.ResponseTabText);
|
||||
this.optionsTab = sqlops.window.modelviewdialog.createTab(AlertDialog.OptionsTabText);
|
||||
|
||||
this.initializeGeneralTab(this.databases, dialog);
|
||||
this.initializeResponseTab();
|
||||
this.initializeOptionsTab();
|
||||
|
||||
dialog.content = [this.generalTab, this.responseTab, this.optionsTab];
|
||||
}
|
||||
|
||||
private initializeGeneralTab(databases: string[], dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
this.generalTab.registerContent(async view => {
|
||||
// create controls
|
||||
this.nameTextBox = view.modelBuilder.inputBox().component();
|
||||
this.nameTextBox.required = true;
|
||||
this.nameTextBox.onTextChanged(() => {
|
||||
if (this.nameTextBox.value.length > 0) {
|
||||
dialog.okButton.enabled = true;
|
||||
} else {
|
||||
dialog.okButton.enabled = false;
|
||||
}
|
||||
});
|
||||
this.enabledCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.EnabledCheckboxLabel
|
||||
}).component();
|
||||
|
||||
this.enabledCheckBox.checked = true;
|
||||
|
||||
this.databaseDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: databases[0],
|
||||
values: databases,
|
||||
width: '100%'
|
||||
}).component();
|
||||
|
||||
this.typeDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: '',
|
||||
values: AlertDialog.AlertTypes,
|
||||
width: '100%'
|
||||
}).component();
|
||||
|
||||
this.severityRadioButton = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'serverity',
|
||||
name: 'alertTypeOptions',
|
||||
label: AlertDialog.SeverityLabel,
|
||||
checked: true
|
||||
}).component();
|
||||
this.severityRadioButton.checked = true;
|
||||
|
||||
this.severityDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: AlertDialog.AlertSeverities[0],
|
||||
values: AlertDialog.AlertSeverities,
|
||||
width: '100%'
|
||||
}).component();
|
||||
|
||||
this.errorNumberRadioButton = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'errorNumber',
|
||||
name: 'alertTypeOptions',
|
||||
label: AlertDialog.ErrorNumberLabel
|
||||
}).component();
|
||||
|
||||
this.errorNumberTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
width: '100%'
|
||||
})
|
||||
.component();
|
||||
this.errorNumberTextBox.enabled = false;
|
||||
|
||||
this.errorNumberRadioButton.onDidClick(() => {
|
||||
this.errorNumberTextBox.enabled = true;
|
||||
this.severityDropDown.enabled = false;
|
||||
});
|
||||
|
||||
this.severityRadioButton.onDidClick(() => {
|
||||
this.errorNumberTextBox.enabled = false;
|
||||
this.severityDropDown.enabled = true;
|
||||
});
|
||||
|
||||
this.raiseAlertMessageCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.RaiseIfMessageContainsLabel
|
||||
}).component();
|
||||
|
||||
this.raiseAlertMessageTextBox = view.modelBuilder.inputBox().component();
|
||||
this.raiseAlertMessageTextBox.enabled = false;
|
||||
|
||||
this.raiseAlertMessageCheckBox.onChanged(() => {
|
||||
this.raiseAlertMessageTextBox.enabled = this.raiseAlertMessageCheckBox.checked;
|
||||
});
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: AlertDialog.NameLabel
|
||||
}, {
|
||||
component: this.enabledCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.typeDropDown,
|
||||
title: AlertDialog.TypeLabel
|
||||
}, {
|
||||
components: [{
|
||||
component: this.databaseDropDown,
|
||||
title: AlertDialog.DatabaseLabel
|
||||
},
|
||||
{
|
||||
component: this.severityRadioButton,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.severityDropDown,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.errorNumberRadioButton,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.errorNumberTextBox,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.raiseAlertMessageCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.raiseAlertMessageTextBox,
|
||||
title: AlertDialog.MessageTextLabel
|
||||
}],
|
||||
title: AlertDialog.EventAlertText
|
||||
}
|
||||
]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
// initialize control values
|
||||
this.nameTextBox.value = this.model.name;
|
||||
this.raiseAlertMessageTextBox.value = this.model.eventDescriptionKeyword;
|
||||
this.typeDropDown.value = this.model.alertType;
|
||||
this.enabledCheckBox.checked = this.model.isEnabled;
|
||||
|
||||
if (this.model.messageId > 0) {
|
||||
this.errorNumberRadioButton.checked = true;
|
||||
this.errorNumberTextBox.value = this.model.messageId.toString();
|
||||
}
|
||||
|
||||
if (this.model.severity > 0) {
|
||||
this.severityRadioButton.checked = true;
|
||||
this.severityDropDown.value = this.severityDropDown.values[this.model.severity-1];
|
||||
}
|
||||
|
||||
if (this.model.databaseName) {
|
||||
let idx = this.databases.indexOf(this.model.databaseName);
|
||||
if (idx >= 0) {
|
||||
this.databaseDropDown.value = this.databases[idx];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private initializeResponseTab() {
|
||||
this.responseTab.registerContent(async view => {
|
||||
this.executeJobCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.ExecuteJobCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.executeJobTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({ width: 375 })
|
||||
.component();
|
||||
this.executeJobTextBox.enabled = false;
|
||||
this.newJobButton = view.modelBuilder.button().withProperties({
|
||||
label: AlertDialog.NewJobButtonLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
this.newJobButton.enabled = false;
|
||||
this.newJobButton.onDidClick(() => {
|
||||
let jobDialog = new JobDialog(this.ownerUri);
|
||||
jobDialog.openDialog();
|
||||
});
|
||||
|
||||
this.executeJobCheckBox.onChanged(() => {
|
||||
if (this.executeJobCheckBox.checked) {
|
||||
this.executeJobTextBox.enabled = true;
|
||||
this.newJobButton.enabled = true;
|
||||
} else {
|
||||
this.executeJobTextBox.enabled = false;
|
||||
this.newJobButton.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
let executeJobContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.executeJobTextBox,
|
||||
title: AlertDialog.ExecuteJobTextBoxLabel
|
||||
}, {
|
||||
component: this.newJobButton,
|
||||
title: AlertDialog.NewJobButtonLabel
|
||||
}], { componentWidth: '100%'}).component();
|
||||
|
||||
this.notifyOperatorsCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.NotifyOperatorsTextBoxLabel
|
||||
}).component();
|
||||
|
||||
this.operatorsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
AlertDialog.OperatorNameColumnLabel,
|
||||
AlertDialog.OperatorEmailColumnLabel,
|
||||
AlertDialog.OperatorPagerColumnLabel
|
||||
],
|
||||
data: [],
|
||||
height: 500,
|
||||
width: 375
|
||||
}).component();
|
||||
|
||||
this.newOperatorButton = view.modelBuilder.button().withProperties({
|
||||
label: AlertDialog.NewOperatorButtonLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.operatorsTable.enabled = false;
|
||||
this.newOperatorButton.enabled = false;
|
||||
|
||||
this.newOperatorButton.onDidClick(() => {
|
||||
let operatorDialog = new OperatorDialog(this.ownerUri);
|
||||
operatorDialog.openDialog();
|
||||
});
|
||||
|
||||
this.notifyOperatorsCheckBox.onChanged(() => {
|
||||
if (this.notifyOperatorsCheckBox.checked) {
|
||||
this.operatorsTable.enabled = true;
|
||||
this.newOperatorButton.enabled = true;
|
||||
} else {
|
||||
this.operatorsTable.enabled = false;
|
||||
this.newOperatorButton.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
let notifyOperatorContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.operatorsTable,
|
||||
title: AlertDialog.OperatorListLabel
|
||||
}, {
|
||||
component: this.newOperatorButton,
|
||||
title: ''
|
||||
}], { componentWidth: '100%'}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.executeJobCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: executeJobContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.notifyOperatorsCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: notifyOperatorContainer,
|
||||
title: ''
|
||||
}])
|
||||
.withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeOptionsTab() {
|
||||
this.optionsTab.registerContent(async view => {
|
||||
|
||||
this.includeErrorInEmailTextBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.IncludeErrorInEmailCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.includeErrorInPagerTextBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.IncludeErrorInPagerCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.additionalMessageTextBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
this.delayMinutesTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'number',
|
||||
placeHolder: 0
|
||||
})
|
||||
.component();
|
||||
|
||||
this.delaySecondsTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'number',
|
||||
placeHolder: 0
|
||||
})
|
||||
.component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.includeErrorInEmailTextBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.includeErrorInPagerTextBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.additionalMessageTextBox,
|
||||
title: AlertDialog.AdditionalMessageTextBoxLabel
|
||||
}, {
|
||||
component: this.delayMinutesTextBox,
|
||||
title: AlertDialog.DelayMinutesTextBoxLabel
|
||||
}, {
|
||||
component: this.delaySecondsTextBox,
|
||||
title: AlertDialog.DelaySecondsTextBoxLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private getSeverityNumber(): number {
|
||||
let selected = this.getDropdownValue(this.severityDropDown);
|
||||
let severityNumber: number = 0;
|
||||
if (selected) {
|
||||
let index = AlertDialog.AlertSeverities.indexOf(selected);
|
||||
if (index >= 0) {
|
||||
severityNumber = index + 1;
|
||||
}
|
||||
}
|
||||
return severityNumber;
|
||||
}
|
||||
|
||||
protected updateModel() {
|
||||
this.model.name = this.nameTextBox.value;
|
||||
this.model.isEnabled = this.enabledCheckBox.checked;
|
||||
|
||||
this.model.alertType = this.getDropdownValue(this.typeDropDown);
|
||||
let databaseName = this.getDropdownValue(this.databaseDropDown);
|
||||
this.model.databaseName = (databaseName !== AlertDialog.AllDatabases) ? databaseName : undefined;
|
||||
|
||||
if (this.severityRadioButton.checked) {
|
||||
this.model.severity = this.getSeverityNumber();
|
||||
this.model.messageId = 0;
|
||||
} else {
|
||||
this.model.severity = 0;
|
||||
this.model.messageId = +this.errorNumberTextBox.value;
|
||||
}
|
||||
|
||||
if (this.raiseAlertMessageCheckBox.checked) {
|
||||
this.model.eventDescriptionKeyword = this.raiseAlertMessageTextBox.value;
|
||||
} else {
|
||||
this.model.eventDescriptionKeyword = '';
|
||||
}
|
||||
let minutes = this.delayMinutesTextBox.value ? +this.delayMinutesTextBox.value : 0;
|
||||
let seconds = this.delaySecondsTextBox.value ? +this.delaySecondsTextBox : 0;
|
||||
this.model.delayBetweenResponses = minutes + seconds;
|
||||
|
||||
}
|
||||
}
|
||||
466
extensions/agent/src/dialogs/jobDialog.ts
Normal file
466
extensions/agent/src/dialogs/jobDialog.ts
Normal file
@@ -0,0 +1,466 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { JobData } from '../data/jobData';
|
||||
import { JobStepDialog } from './jobStepDialog';
|
||||
import { PickScheduleDialog } from './pickScheduleDialog';
|
||||
import { AlertDialog } from './alertDialog';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class JobDialog extends AgentDialog<JobData> {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
private static readonly CreateDialogTitle: string = localize('jobDialog.newJob', 'New Job');
|
||||
private static readonly EditDialogTitle: string = localize('jobDialog.editJob', 'Edit Job');
|
||||
private readonly GeneralTabText: string = localize('jobDialog.general', 'General');
|
||||
private readonly StepsTabText: string = localize('jobDialog.steps', 'Steps');
|
||||
private readonly SchedulesTabText: string = localize('jobDialog.schedules', 'Schedules');
|
||||
private readonly AlertsTabText: string = localize('jobDialog.alerts', 'Alerts');
|
||||
private readonly NotificationsTabText: string = localize('jobDialog.notifications', 'Notifications');
|
||||
private readonly BlankJobNameErrorText: string = localize('jobDialog.blankJobNameError', 'The name of the job cannot be blank.');
|
||||
|
||||
// General tab strings
|
||||
private readonly NameTextBoxLabel: string = localize('jobDialog.name', 'Name');
|
||||
private readonly OwnerTextBoxLabel: string = localize('jobDialog.owner', 'Owner');
|
||||
private readonly CategoryDropdownLabel: string = localize('jobDialog.category', 'Category');
|
||||
private readonly DescriptionTextBoxLabel: string = localize('jobDialog.description', 'Description');
|
||||
private readonly EnabledCheckboxLabel: string = localize('jobDialog.enabled', 'Enabled');
|
||||
|
||||
// Steps tab strings
|
||||
private readonly JobStepsTopLabelString: string = localize('jobDialog.jobStepList', 'Job step list');
|
||||
private readonly StepsTable_StepColumnString: string = localize('jobDialog.step', 'Step');
|
||||
private readonly StepsTable_NameColumnString: string = localize('jobDialog.name', 'Name');
|
||||
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
|
||||
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
|
||||
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
|
||||
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...');
|
||||
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit');
|
||||
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete');
|
||||
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
|
||||
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Up');
|
||||
|
||||
// Notifications tab strings
|
||||
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
|
||||
private readonly EmailCheckBoxString: string = localize('jobDialog.email', 'Email');
|
||||
private readonly PagerCheckBoxString: string = localize('jobDialog.page', 'Page');
|
||||
private readonly EventLogCheckBoxString: string = localize('jobDialog.eventLogCheckBoxLabel', 'Write to the Windows Application event log');
|
||||
private readonly DeleteJobCheckBoxString: string = localize('jobDialog.deleteJobLabel', 'Automatically delete job');
|
||||
|
||||
// Schedules tab strings
|
||||
private readonly SchedulesTopLabelString: string = localize('jobDialog.schedulesaLabel', 'Schedules list');
|
||||
private readonly PickScheduleButtonString: string = localize('jobDialog.pickSchedule', 'Pick Schedule');
|
||||
private readonly ScheduleNameLabelString: string = localize('jobDialog.scheduleNameLabel', 'Schedule Name');
|
||||
|
||||
// Alerts tab strings
|
||||
private readonly AlertsTopLabelString: string = localize('jobDialog.alertsList', 'Alerts list');
|
||||
private readonly NewAlertButtonString: string = localize('jobDialog.newAlert', 'New Alert');
|
||||
private readonly AlertNameLabelString: string = localize('jobDialog.alertNameLabel', 'Alert Name');
|
||||
|
||||
// UI Components
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private stepsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private alertsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private schedulesTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
// General tab controls
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private ownerTextBox: sqlops.InputBoxComponent;
|
||||
private categoryDropdown: sqlops.DropDownComponent;
|
||||
private descriptionTextBox: sqlops.InputBoxComponent;
|
||||
private enabledCheckBox: sqlops.CheckBoxComponent;
|
||||
|
||||
// Steps tab controls
|
||||
private stepsTable: sqlops.TableComponent;
|
||||
private newStepButton: sqlops.ButtonComponent;
|
||||
private moveStepUpButton: sqlops.ButtonComponent;
|
||||
private moveStepDownButton: sqlops.ButtonComponent;
|
||||
private editStepButton: sqlops.ButtonComponent;
|
||||
private deleteStepButton: sqlops.ButtonComponent;
|
||||
|
||||
// Notifications tab controls
|
||||
private notificationsTabTopLabel: sqlops.TextComponent;
|
||||
private emailCheckBox: sqlops.CheckBoxComponent;
|
||||
private emailOperatorDropdown: sqlops.DropDownComponent;
|
||||
private emailConditionDropdown: sqlops.DropDownComponent;
|
||||
private pagerCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerOperatorDropdown: sqlops.DropDownComponent;
|
||||
private pagerConditionDropdown: sqlops.DropDownComponent;
|
||||
private eventLogCheckBox: sqlops.CheckBoxComponent;
|
||||
private eventLogConditionDropdown: sqlops.DropDownComponent;
|
||||
private deleteJobCheckBox: sqlops.CheckBoxComponent;
|
||||
private deleteJobConditionDropdown: sqlops.DropDownComponent;
|
||||
|
||||
// Schedule tab controls
|
||||
private schedulesTable: sqlops.TableComponent;
|
||||
private pickScheduleButton: sqlops.ButtonComponent;
|
||||
|
||||
// Alert tab controls
|
||||
private alertsTable: sqlops.TableComponent;
|
||||
private newAlertButton: sqlops.ButtonComponent;
|
||||
|
||||
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
|
||||
super(
|
||||
ownerUri,
|
||||
new JobData(ownerUri, jobInfo),
|
||||
jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle);
|
||||
}
|
||||
|
||||
protected async initializeDialog() {
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
|
||||
this.stepsTab = sqlops.window.modelviewdialog.createTab(this.StepsTabText);
|
||||
this.alertsTab = sqlops.window.modelviewdialog.createTab(this.AlertsTabText);
|
||||
this.schedulesTab = sqlops.window.modelviewdialog.createTab(this.SchedulesTabText);
|
||||
this.notificationsTab = sqlops.window.modelviewdialog.createTab(this.NotificationsTabText);
|
||||
this.initializeGeneralTab();
|
||||
this.initializeStepsTab();
|
||||
this.initializeAlertsTab();
|
||||
this.initializeSchedulesTab();
|
||||
this.initializeNotificationsTab();
|
||||
this.dialog.content = [this.generalTab, this.stepsTab, this.schedulesTab, this.alertsTab, this.notificationsTab];
|
||||
|
||||
this.dialog.registerCloseValidator(() => {
|
||||
this.updateModel();
|
||||
let validationResult = this.model.validate();
|
||||
if (!validationResult.valid) {
|
||||
// TODO: Show Error Messages
|
||||
console.error(validationResult.errorMessages.join(','));
|
||||
}
|
||||
|
||||
return validationResult.valid;
|
||||
});
|
||||
}
|
||||
|
||||
private initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
this.nameTextBox = view.modelBuilder.inputBox().component();
|
||||
this.nameTextBox.required = true;
|
||||
this.nameTextBox.onTextChanged(() => {
|
||||
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
||||
this.dialog.message = null;
|
||||
}
|
||||
});
|
||||
this.ownerTextBox = view.modelBuilder.inputBox().component();
|
||||
this.categoryDropdown = view.modelBuilder.dropDown().component();
|
||||
this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({
|
||||
multiline: true,
|
||||
height: 200
|
||||
}).component();
|
||||
this.enabledCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: this.EnabledCheckboxLabel
|
||||
}).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: this.NameTextBoxLabel
|
||||
}, {
|
||||
component: this.ownerTextBox,
|
||||
title: this.OwnerTextBoxLabel
|
||||
}, {
|
||||
component: this.categoryDropdown,
|
||||
title: this.CategoryDropdownLabel
|
||||
}, {
|
||||
component: this.descriptionTextBox,
|
||||
title: this.DescriptionTextBoxLabel
|
||||
}, {
|
||||
component: this.enabledCheckBox,
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
this.nameTextBox.value = this.model.name;
|
||||
this.ownerTextBox.value = this.model.defaultOwner;
|
||||
this.categoryDropdown.values = this.model.jobCategories;
|
||||
|
||||
let idx: number = undefined;
|
||||
if (this.model.category && this.model.category !== '') {
|
||||
idx = this.model.jobCategories.indexOf(this.model.category);
|
||||
}
|
||||
this.categoryDropdown.value = this.model.jobCategories[idx > 0 ? idx : 0];
|
||||
|
||||
this.enabledCheckBox.checked = this.model.enabled;
|
||||
this.descriptionTextBox.value = this.model.description;
|
||||
});
|
||||
}
|
||||
|
||||
private initializeStepsTab() {
|
||||
this.stepsTab.registerContent(async view => {
|
||||
this.stepsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.StepsTable_StepColumnString,
|
||||
this.StepsTable_NameColumnString,
|
||||
this.StepsTable_TypeColumnString,
|
||||
this.StepsTable_SuccessColumnString,
|
||||
this.StepsTable_FailureColumnString
|
||||
],
|
||||
data: [],
|
||||
height: 430
|
||||
}).component();
|
||||
|
||||
this.moveStepUpButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.MoveStepUpButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.moveStepDownButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.MoveStepDownButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.moveStepUpButton.enabled = false;
|
||||
this.moveStepDownButton.enabled = false;
|
||||
|
||||
this.newStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.NewStepButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.newStepButton.onDidClick((e)=>{
|
||||
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
||||
let stepDialog = new JobStepDialog(this.model.ownerUri, this.nameTextBox.value, '' , 1, this.model);
|
||||
stepDialog.openNewStepDialog();
|
||||
} else {
|
||||
this.dialog.message = { text: this.BlankJobNameErrorText };
|
||||
}
|
||||
});
|
||||
|
||||
this.editStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.EditStepButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.deleteStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.DeleteStepButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.stepsTable,
|
||||
title: this.JobStepsTopLabelString,
|
||||
actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeAlertsTab() {
|
||||
this.alertsTab.registerContent(async view => {
|
||||
this.alertsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.AlertNameLabelString
|
||||
],
|
||||
data: [],
|
||||
height: 430,
|
||||
width: 400
|
||||
}).component();
|
||||
|
||||
this.newAlertButton = view.modelBuilder.button().withProperties({
|
||||
label: this.NewAlertButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.newAlertButton.onDidClick((e)=>{
|
||||
let alertDialog = new AlertDialog(this.model.ownerUri, null, []);
|
||||
alertDialog.onSuccess((dialogModel) => {
|
||||
});
|
||||
alertDialog.openDialog();
|
||||
});
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.alertsTable,
|
||||
title: this.AlertsTopLabelString,
|
||||
actions: [this.newAlertButton]
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeSchedulesTab() {
|
||||
this.schedulesTab.registerContent(async view => {
|
||||
this.schedulesTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.ScheduleNameLabelString
|
||||
],
|
||||
data: [],
|
||||
height: 430,
|
||||
width: 420
|
||||
}).component();
|
||||
|
||||
this.pickScheduleButton = view.modelBuilder.button().withProperties({
|
||||
label: this.PickScheduleButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.pickScheduleButton.onDidClick((e)=>{
|
||||
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri);
|
||||
pickScheduleDialog.onSuccess((dialogModel) => {
|
||||
let selectedSchedule = dialogModel.selectedSchedule;
|
||||
if (selectedSchedule) {
|
||||
this.model.addJobSchedule(selectedSchedule);
|
||||
this.populateScheduleTable();
|
||||
}
|
||||
});
|
||||
pickScheduleDialog.showDialog();
|
||||
});
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
title: this.SchedulesTopLabelString,
|
||||
actions: [this.pickScheduleButton]
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
this.populateScheduleTable();
|
||||
});
|
||||
}
|
||||
|
||||
private populateScheduleTable() {
|
||||
if (this.model.jobSchedules) {
|
||||
let data: any[][] = [];
|
||||
for (let i = 0; i < this.model.jobSchedules.length; ++i) {
|
||||
let schedule = this.model.jobSchedules[i];
|
||||
data[i] = [ schedule.name ];
|
||||
}
|
||||
this.schedulesTable.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private initializeNotificationsTab() {
|
||||
this.notificationsTab.registerContent(async view => {
|
||||
|
||||
this.notificationsTabTopLabel = view.modelBuilder.text().withProperties({ value: this.NotificationsTabTopLabelString }).component();
|
||||
this.emailCheckBox = view.modelBuilder.checkBox().withProperties({
|
||||
label: this.EmailCheckBoxString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.pagerCheckBox = view.modelBuilder.checkBox().withProperties({
|
||||
label: this.PagerCheckBoxString,
|
||||
width: 80
|
||||
}).component();
|
||||
this.eventLogCheckBox = view.modelBuilder.checkBox().withProperties({
|
||||
label: this.EventLogCheckBoxString,
|
||||
width: 250
|
||||
}).component();
|
||||
this.deleteJobCheckBox = view.modelBuilder.checkBox().withProperties({
|
||||
label: this.DeleteJobCheckBoxString,
|
||||
width: 250
|
||||
}).component();
|
||||
|
||||
this.emailCheckBox.onChanged(() => {
|
||||
this.emailConditionDropdown.enabled = this.emailCheckBox.checked;
|
||||
this.emailOperatorDropdown.enabled = this.emailCheckBox.checked;
|
||||
});
|
||||
|
||||
this.pagerCheckBox.onChanged(() => {
|
||||
this.pagerConditionDropdown.enabled = this.pagerCheckBox.checked;
|
||||
this.pagerOperatorDropdown.enabled = this.pagerCheckBox.checked;
|
||||
});
|
||||
this.eventLogCheckBox.onChanged(() => {
|
||||
this.eventLogConditionDropdown.enabled = this.eventLogCheckBox.checked;
|
||||
});
|
||||
|
||||
this.deleteJobCheckBox.onChanged(() => {
|
||||
this.deleteJobConditionDropdown.enabled = this.deleteJobCheckBox.checked;
|
||||
});
|
||||
|
||||
this.emailOperatorDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.pagerOperatorDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.emailConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.pagerConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.eventLogConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.deleteJobConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
|
||||
let emailContainer = this.createRowContainer(view).withItems([this.emailCheckBox, this.emailOperatorDropdown, this.emailConditionDropdown]).component();
|
||||
|
||||
let pagerContainer = this.createRowContainer(view).withItems([this.pagerCheckBox, this.pagerOperatorDropdown, this.pagerConditionDropdown]).component();
|
||||
|
||||
let eventLogContainer = this.createRowContainer(view).withItems([this.eventLogCheckBox, this.eventLogConditionDropdown]).component();
|
||||
|
||||
let deleteJobContainer = this.createRowContainer(view).withItems([this.deleteJobCheckBox, this.deleteJobConditionDropdown]).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer().withFormItems([
|
||||
{
|
||||
components:
|
||||
[{
|
||||
component: emailContainer,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: pagerContainer,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: eventLogContainer,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: deleteJobContainer,
|
||||
title: ''
|
||||
}], title: this.NotificationsTabTopLabelString}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
this.emailConditionDropdown.values = this.model.JobCompletionActionConditions;
|
||||
this.pagerConditionDropdown.values = this.model.JobCompletionActionConditions;
|
||||
this.eventLogConditionDropdown.values = this.model.JobCompletionActionConditions;
|
||||
this.deleteJobConditionDropdown.values = this.model.JobCompletionActionConditions;
|
||||
this.setConditionDropdownSelectedValue(this.emailConditionDropdown, this.model.emailLevel);
|
||||
this.setConditionDropdownSelectedValue(this.pagerConditionDropdown, this.model.pageLevel);
|
||||
this.setConditionDropdownSelectedValue(this.eventLogConditionDropdown, this.model.eventLogLevel);
|
||||
this.setConditionDropdownSelectedValue(this.deleteJobConditionDropdown, this.model.deleteLevel);
|
||||
this.emailOperatorDropdown.values = this.model.operators;
|
||||
this.pagerOperatorDropdown.values = this.model.operators;
|
||||
this.emailCheckBox.checked = false;
|
||||
this.pagerCheckBox.checked = false;
|
||||
this.eventLogCheckBox.checked = false;
|
||||
this.deleteJobCheckBox.checked = false;
|
||||
this.emailOperatorDropdown.enabled = false;
|
||||
this.pagerOperatorDropdown.enabled = false;
|
||||
this.emailConditionDropdown.enabled = false;
|
||||
this.pagerConditionDropdown.enabled = false;
|
||||
this.eventLogConditionDropdown.enabled = false;
|
||||
this.deleteJobConditionDropdown.enabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
private createRowContainer(view: sqlops.ModelView): sqlops.FlexBuilder {
|
||||
return view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'left',
|
||||
justifyContent: 'space-between'
|
||||
});
|
||||
}
|
||||
|
||||
protected updateModel() {
|
||||
this.model.name = this.nameTextBox.value;
|
||||
this.model.owner = this.ownerTextBox.value;
|
||||
this.model.enabled = this.enabledCheckBox.checked;
|
||||
this.model.description = this.descriptionTextBox.value;
|
||||
this.model.category = this.getDropdownValue(this.categoryDropdown);
|
||||
this.model.emailLevel = this.getActualConditionValue(this.emailCheckBox, this.emailConditionDropdown);
|
||||
this.model.operatorToEmail = this.getDropdownValue(this.emailOperatorDropdown);
|
||||
this.model.operatorToPage = this.getDropdownValue(this.pagerOperatorDropdown);
|
||||
this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown);
|
||||
this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown);
|
||||
this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown);
|
||||
}
|
||||
}
|
||||
510
extensions/agent/src/dialogs/jobStepDialog.ts
Normal file
510
extensions/agent/src/dialogs/jobStepDialog.ts
Normal file
@@ -0,0 +1,510 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { JobStepData } from '../data/jobStepData';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { JobData } from '../data/jobData';
|
||||
const path = require('path');
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class JobStepDialog {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
//
|
||||
private readonly DialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step');
|
||||
private readonly FileBrowserDialogTitle: string = localize('jobStepDialog.fileBrowserTitle', 'Locate Database Files - ');
|
||||
private readonly OkButtonText: string = localize('jobStepDialog.ok', 'OK');
|
||||
private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel');
|
||||
private readonly GeneralTabText: string = localize('jobStepDialog.general', 'General');
|
||||
private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
|
||||
private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
|
||||
private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
|
||||
private readonly NextButtonText: string = localize('jobStepDialog.next', 'Next');
|
||||
private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
|
||||
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
|
||||
private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
|
||||
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
|
||||
|
||||
// General Control Titles
|
||||
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
|
||||
private readonly TypeLabelString: string = localize('jobStepDialog.typeLabel', 'Type');
|
||||
private readonly RunAsLabelString: string = localize('jobStepDialog.runAsLabel', 'Run as');
|
||||
private readonly DatabaseLabelString: string = localize('jobStepDialog.databaseLabel', 'Database');
|
||||
private readonly CommandLabelString: string = localize('jobStepDialog.commandLabel', 'Command');
|
||||
|
||||
// Advanced Control Titles
|
||||
private readonly SuccessActionLabel: string = localize('jobStepDialog.successAction', 'On success action');
|
||||
private readonly FailureActionLabel: string = localize('jobStepDialog.failureAction', 'On failure action');
|
||||
private readonly RunAsUserLabel: string = localize('jobStepDialog.runAsUser', 'Run as user');
|
||||
private readonly RetryAttemptsLabel: string = localize('jobStepDialog.retryAttempts', 'Retry Attempts');
|
||||
private readonly RetryIntervalLabel: string = localize('jobStepDialog.retryInterval', 'Retry Interval (minutes)');
|
||||
private readonly LogToTableLabel: string = localize('jobStepDialog.logToTable', 'Log to table');
|
||||
private readonly AppendExistingTableEntryLabel: string = localize('jobStepDialog.appendExistingTableEntry', 'Append output to exisiting entry in table');
|
||||
private readonly IncludeStepOutputHistoryLabel: string = localize('jobStepDialog.includeStepOutputHistory', 'Include step output in history');
|
||||
private readonly OutputFileNameLabel: string = localize('jobStepDialog.outputFile', 'Output File');
|
||||
private readonly AppendOutputToFileLabel: string = localize('jobStepDialog.appendOutputToFile', 'Append output to existing file');
|
||||
|
||||
// File Browser Control Titles
|
||||
private readonly SelectedPathLabelString: string = localize('jobStepDialog.selectedPath', 'Selected path');
|
||||
private readonly FilesOfTypeLabelString: string = localize('jobStepDialog.filesOfType', 'Files of type');
|
||||
private readonly FileNameLabelString: string = localize('jobStepDialog.fileName', 'File name');
|
||||
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 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');
|
||||
|
||||
// UI Components
|
||||
|
||||
// Dialogs
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private fileBrowserDialog: sqlops.window.modelviewdialog.Dialog;
|
||||
|
||||
// Dialog tabs
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private advancedTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
//Input boxes
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private commandTextBox: sqlops.InputBoxComponent;
|
||||
private selectedPathTextBox: sqlops.InputBoxComponent;
|
||||
private retryAttemptsBox: sqlops.InputBoxComponent;
|
||||
private retryIntervalBox: sqlops.InputBoxComponent;
|
||||
private outputFileNameBox: sqlops.InputBoxComponent;
|
||||
private fileBrowserNameBox: sqlops.InputBoxComponent;
|
||||
private userInputBox: sqlops.InputBoxComponent;
|
||||
|
||||
// Dropdowns
|
||||
private typeDropdown: sqlops.DropDownComponent;
|
||||
private runAsDropdown: sqlops.DropDownComponent;
|
||||
private databaseDropdown: sqlops.DropDownComponent;
|
||||
private successActionDropdown: sqlops.DropDownComponent;
|
||||
private failureActionDropdown: sqlops.DropDownComponent;
|
||||
private fileTypeDropdown: sqlops.DropDownComponent;
|
||||
|
||||
// Buttons
|
||||
private openButton: sqlops.ButtonComponent;
|
||||
private parseButton: sqlops.ButtonComponent;
|
||||
private nextButton: sqlops.ButtonComponent;
|
||||
private previousButton: sqlops.ButtonComponent;
|
||||
private outputFileBrowserButton: sqlops.ButtonComponent;
|
||||
|
||||
// Checkbox
|
||||
private appendToExistingFileCheckbox: sqlops.CheckBoxComponent;
|
||||
private logToTableCheckbox: sqlops.CheckBoxComponent;
|
||||
|
||||
private fileBrowserTree: sqlops.FileBrowserTreeComponent;
|
||||
private jobModel: JobData;
|
||||
private model: JobStepData;
|
||||
private ownerUri: string;
|
||||
private jobName: string;
|
||||
private server: string;
|
||||
private stepId: number;
|
||||
|
||||
constructor(
|
||||
ownerUri: string,
|
||||
jobName: string,
|
||||
server: string,
|
||||
stepId: number,
|
||||
jobModel?: JobData
|
||||
) {
|
||||
this.model = new JobStepData(ownerUri);
|
||||
this.stepId = stepId;
|
||||
this.ownerUri = ownerUri;
|
||||
this.jobName = jobName;
|
||||
this.server = server;
|
||||
this.jobModel = jobModel;
|
||||
}
|
||||
|
||||
private initializeUIComponents() {
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
|
||||
this.advancedTab = sqlops.window.modelviewdialog.createTab(this.AdvancedTabText);
|
||||
this.dialog.content = [this.generalTab, this.advancedTab];
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
this.dialog.okButton.label = this.OkButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
}
|
||||
|
||||
private createCommands(view, queryProvider: sqlops.QueryProvider) {
|
||||
this.openButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.OpenCommandText,
|
||||
width: '80px',
|
||||
isFile: true
|
||||
}).component();
|
||||
this.parseButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.ParseCommandText,
|
||||
width: '80px',
|
||||
isFile: false
|
||||
}).component();
|
||||
this.openButton.onDidClick(e => {
|
||||
let queryContent = e;
|
||||
this.commandTextBox.value = queryContent;
|
||||
});
|
||||
this.parseButton.onDidClick(e => {
|
||||
if (this.commandTextBox.value) {
|
||||
queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => {
|
||||
if (result && result.parseable) {
|
||||
this.dialog.message = { text: this.SuccessfulParseText, level: 2};
|
||||
} else if (result && !result.parseable) {
|
||||
this.dialog.message = { text: this.FailureParseText };
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.commandTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
height: 300,
|
||||
width: 400,
|
||||
multiline: true,
|
||||
inputType: 'text'
|
||||
})
|
||||
.component();
|
||||
this.nextButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.NextButtonText,
|
||||
enabled: false,
|
||||
width: '80px'
|
||||
}).component();
|
||||
this.previousButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.PreviousButtonText,
|
||||
enabled: false,
|
||||
width: '80px'
|
||||
}).component();
|
||||
}
|
||||
|
||||
private createGeneralTab(databases: string[], queryProvider: sqlops.QueryProvider) {
|
||||
this.generalTab.registerContent(async (view) => {
|
||||
this.nameTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
}).component();
|
||||
this.nameTextBox.required = true;
|
||||
this.nameTextBox.onTextChanged(() => {
|
||||
if (this.nameTextBox.value.length > 0) {
|
||||
this.dialog.message = null;
|
||||
}
|
||||
});
|
||||
|
||||
this.typeDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: this.TSQLScript,
|
||||
values: [this.TSQLScript]
|
||||
})
|
||||
.component();
|
||||
this.runAsDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: '',
|
||||
values: ['']
|
||||
})
|
||||
.component();
|
||||
this.runAsDropdown.enabled = false;
|
||||
this.typeDropdown.onValueChanged((type) => {
|
||||
if (type.selected !== this.TSQLScript) {
|
||||
this.runAsDropdown.value = this.AgentServiceAccount;
|
||||
this.runAsDropdown.values = [this.runAsDropdown.value];
|
||||
} else {
|
||||
this.runAsDropdown.value = '';
|
||||
this.runAsDropdown.values = [''];
|
||||
}
|
||||
});
|
||||
this.databaseDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: databases[0],
|
||||
values: databases
|
||||
}).component();
|
||||
|
||||
// create the commands section
|
||||
this.createCommands(view, queryProvider);
|
||||
|
||||
let buttonContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: 420
|
||||
}).withItems([this.openButton, this.parseButton, this.previousButton, this.nextButton], {
|
||||
flex: '1 1 50%'
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: this.StepNameLabelString
|
||||
}, {
|
||||
component: this.typeDropdown,
|
||||
title: this.TypeLabelString
|
||||
}, {
|
||||
component: this.runAsDropdown,
|
||||
title: this.RunAsLabelString
|
||||
}, {
|
||||
component: this.databaseDropdown,
|
||||
title: this.DatabaseLabelString
|
||||
}, {
|
||||
component: this.commandTextBox,
|
||||
title: this.CommandLabelString,
|
||||
actions: [buttonContainer]
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: 420
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
await view.initializeModel(formWrapper);
|
||||
});
|
||||
}
|
||||
|
||||
private createAdvancedTab() {
|
||||
this.advancedTab.registerContent(async (view) => {
|
||||
this.successActionDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
width: '100%',
|
||||
value: this.NextStep,
|
||||
values: [this.NextStep, this.QuitJobReportingSuccess, this.QuitJobReportingFailure]
|
||||
})
|
||||
.component();
|
||||
let retryFlexContainer = this.createRetryCounters(view);
|
||||
|
||||
this.failureActionDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: this.QuitJobReportingFailure,
|
||||
values: [this.QuitJobReportingFailure, this.NextStep, this.QuitJobReportingSuccess]
|
||||
})
|
||||
.component();
|
||||
let optionsGroup = this.createTSQLOptions(view);
|
||||
this.logToTableCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: this.LogToTableLabel
|
||||
}).component();
|
||||
let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({ label: this.AppendExistingTableEntryLabel }).component();
|
||||
appendToExistingEntryInTableCheckbox.enabled = false;
|
||||
this.logToTableCheckbox.onChanged(e => {
|
||||
appendToExistingEntryInTableCheckbox.enabled = e;
|
||||
});
|
||||
let appendCheckboxContainer = view.modelBuilder.groupContainer()
|
||||
.withItems([appendToExistingEntryInTableCheckbox]).component();
|
||||
let logToTableContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 })
|
||||
.withItems([this.logToTableCheckbox]).component();
|
||||
let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({ label: this.IncludeStepOutputHistoryLabel }).component();
|
||||
this.userInputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({ inputType: 'text', width: '100%' }).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.successActionDropdown,
|
||||
title: this.SuccessActionLabel
|
||||
}, {
|
||||
component: retryFlexContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.failureActionDropdown,
|
||||
title: this.FailureActionLabel
|
||||
}, {
|
||||
component: optionsGroup,
|
||||
title: this.TSQLScript
|
||||
}, {
|
||||
component: logToTableContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: appendCheckboxContainer,
|
||||
title: ' '
|
||||
}, {
|
||||
component: logStepOutputHistoryCheckbox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.userInputBox,
|
||||
title: this.RunAsUserLabel
|
||||
}], {
|
||||
componentWidth: 400
|
||||
}).component();
|
||||
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
view.initializeModel(formWrapper);
|
||||
});
|
||||
}
|
||||
|
||||
private createRetryCounters(view) {
|
||||
this.retryAttemptsBox = view.modelBuilder.inputBox()
|
||||
.withValidation(component => component.value >= 0)
|
||||
.withProperties({
|
||||
inputType: 'number',
|
||||
width: '100%',
|
||||
placeHolder: '0'
|
||||
})
|
||||
.component();
|
||||
this.retryIntervalBox = view.modelBuilder.inputBox()
|
||||
.withValidation(component => component.value >= 0)
|
||||
.withProperties({
|
||||
inputType: 'number',
|
||||
width: '100%',
|
||||
placeHolder: '0'
|
||||
}).component();
|
||||
|
||||
let retryAttemptsContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.retryAttemptsBox,
|
||||
title: this.RetryAttemptsLabel
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
})
|
||||
.component();
|
||||
|
||||
let retryIntervalContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.retryIntervalBox,
|
||||
title: this.RetryIntervalLabel
|
||||
}], {
|
||||
horizontal: false
|
||||
})
|
||||
.component();
|
||||
|
||||
let retryFlexContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
}).withItems([retryAttemptsContainer, retryIntervalContainer]).component();
|
||||
return retryFlexContainer;
|
||||
}
|
||||
|
||||
private openFileBrowserDialog() {
|
||||
let fileBrowserTitle = this.FileBrowserDialogTitle + `${this.server}`;
|
||||
this.fileBrowserDialog = sqlops.window.modelviewdialog.createDialog(fileBrowserTitle);
|
||||
let fileBrowserTab = sqlops.window.modelviewdialog.createTab('File Browser');
|
||||
this.fileBrowserDialog.content = [fileBrowserTab];
|
||||
fileBrowserTab.registerContent(async (view) => {
|
||||
this.fileBrowserTree = view.modelBuilder.fileBrowserTree()
|
||||
.withProperties({ ownerUri: this.ownerUri, width: 420, height: 700 })
|
||||
.component();
|
||||
this.selectedPathTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({ inputType: 'text'})
|
||||
.component();
|
||||
this.fileBrowserTree.onDidChange((args) => {
|
||||
this.selectedPathTextBox.value = args.fullPath;
|
||||
this.fileBrowserNameBox.value = args.isFile ? path.win32.basename(args.fullPath) : '';
|
||||
});
|
||||
this.fileTypeDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: this.AllFilesLabelString,
|
||||
values: [this.AllFilesLabelString]
|
||||
})
|
||||
.component();
|
||||
this.fileBrowserNameBox = view.modelBuilder.inputBox()
|
||||
.withProperties({})
|
||||
.component();
|
||||
let fileBrowserContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.fileBrowserTree,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.selectedPathTextBox,
|
||||
title: this.SelectedPathLabelString
|
||||
}, {
|
||||
component: this.fileTypeDropdown,
|
||||
title: this.FilesOfTypeLabelString
|
||||
}, {
|
||||
component: this.fileBrowserNameBox,
|
||||
title: this.FileNameLabelString
|
||||
}
|
||||
]).component();
|
||||
view.initializeModel(fileBrowserContainer);
|
||||
});
|
||||
this.fileBrowserDialog.okButton.onClick(() => {
|
||||
this.outputFileNameBox.value = path.join(path.dirname(this.selectedPathTextBox.value), this.fileBrowserNameBox.value);
|
||||
});
|
||||
this.fileBrowserDialog.okButton.label = this.OkButtonText;
|
||||
this.fileBrowserDialog.cancelButton.label = this.CancelButtonText;
|
||||
sqlops.window.modelviewdialog.openDialog(this.fileBrowserDialog);
|
||||
}
|
||||
|
||||
private createTSQLOptions(view) {
|
||||
this.outputFileBrowserButton = view.modelBuilder.button()
|
||||
.withProperties({ width: '20px', label: '...' }).component();
|
||||
this.outputFileBrowserButton.onDidClick(() => this.openFileBrowserDialog());
|
||||
this.outputFileNameBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
width: 250,
|
||||
inputType: 'text'
|
||||
}).component();
|
||||
let outputButtonContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
textAlign: 'right',
|
||||
width: '100%'
|
||||
}).withItems([this.outputFileBrowserButton], { flex: '1 1 50%' }).component();
|
||||
let outputFlexBox = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
width: 350
|
||||
}).withItems([this.outputFileNameBox, outputButtonContainer], {
|
||||
flex: '1 1 50%'
|
||||
}).component();
|
||||
this.appendToExistingFileCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: this.AppendOutputToFileLabel
|
||||
}).component();
|
||||
this.appendToExistingFileCheckbox.enabled = false;
|
||||
this.outputFileNameBox.onTextChanged((input) => {
|
||||
if (input !== '') {
|
||||
this.appendToExistingFileCheckbox.enabled = true;
|
||||
} else {
|
||||
this.appendToExistingFileCheckbox.enabled = false;
|
||||
}
|
||||
});
|
||||
let outputFileForm = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: outputFlexBox,
|
||||
title: this.OutputFileNameLabel
|
||||
}, {
|
||||
component: this.appendToExistingFileCheckbox,
|
||||
title: ''
|
||||
}], { horizontal: false, componentWidth: 200 }).component();
|
||||
return outputFileForm;
|
||||
}
|
||||
|
||||
protected execute() {
|
||||
this.model.stepName = this.nameTextBox.value;
|
||||
if (!this.model.stepName || this.model.stepName.length === 0) {
|
||||
this.dialog.message = this.dialog.message = { text: this.BlankStepNameErrorText };
|
||||
return;
|
||||
}
|
||||
this.model.jobName = this.jobName;
|
||||
this.model.id = this.stepId;
|
||||
this.model.server = this.server;
|
||||
this.model.stepName = this.nameTextBox.value;
|
||||
this.model.subSystem = 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.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.outputFileName = this.outputFileNameBox.value;
|
||||
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
|
||||
}
|
||||
|
||||
public async openNewStepDialog() {
|
||||
let databases = await AgentUtils.getDatabases(this.ownerUri);
|
||||
let queryProvider = await AgentUtils.getQueryProvider();
|
||||
this.initializeUIComponents();
|
||||
this.createGeneralTab(databases, queryProvider);
|
||||
this.createAdvancedTab();
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
}
|
||||
404
extensions/agent/src/dialogs/operatorDialog.ts
Normal file
404
extensions/agent/src/dialogs/operatorDialog.ts
Normal file
@@ -0,0 +1,404 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { OperatorData } from '../data/operatorData';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class OperatorDialog extends AgentDialog<OperatorData> {
|
||||
|
||||
// Top level
|
||||
private static readonly CreateDialogTitle: string = localize('createOperator.createOperator', 'Create Operator');
|
||||
private static readonly EditDialogTitle: string = localize('createOperator.editOperator', 'Edit Operator');
|
||||
private static readonly GeneralTabText: string = localize('createOperator.General', 'General');
|
||||
private static readonly NotificationsTabText: string = localize('createOperator.Notifications', 'Notifications');
|
||||
|
||||
// General tab strings
|
||||
private static readonly NameLabel: string = localize('createOperator.Name', 'Name');
|
||||
private static readonly EnabledCheckboxLabel: string = localize('createOperator.Enabled', 'Enabled');
|
||||
private static readonly EmailNameTextLabel: string = localize('createOperator.EmailName', 'E-mail Name');
|
||||
private static readonly PagerEmailNameTextLabel: string = localize('createOperator.PagerEmailName', 'Pager E-mail Name');
|
||||
private static readonly PagerMondayCheckBoxLabel: string = localize('createOperator.PagerMondayCheckBox', 'Monday');
|
||||
private static readonly PagerTuesdayCheckBoxLabel: string = localize('createOperator.PagerTuesdayCheckBox', 'Tuesday');
|
||||
private static readonly PagerWednesdayCheckBoxLabel: string = localize('createOperator.PagerWednesdayCheckBox', 'Wednesday');
|
||||
private static readonly PagerThursdayCheckBoxLabel: string = localize('createOperator.PagerThursdayCheckBox', 'Thursday');
|
||||
private static readonly PagerFridayCheckBoxLabel: string = localize('createOperator.PagerFridayCheckBox', 'Friday ');
|
||||
private static readonly PagerSaturdayCheckBoxLabel: string = localize('createOperator.PagerSaturdayCheckBox', 'Saturday');
|
||||
private static readonly PagerSundayCheckBoxLabel: string = localize('createOperator.PagerSundayCheckBox', 'Sunday');
|
||||
private static readonly WorkdayBeginLabel: string = localize('createOperator.workdayBegin', 'Workday begin');
|
||||
private static readonly WorkdayEndLabel: string = localize('createOperator.workdayEnd', 'Workday end');
|
||||
private static readonly PagerDutyScheduleLabel: string = localize('createOperator.PagerDutySchedule', 'Pager on duty schdule');
|
||||
|
||||
// Notifications tab strings
|
||||
private static readonly AlertsTableLabel: string = localize('createOperator.AlertListHeading', 'Alert list');
|
||||
private static readonly AlertNameColumnLabel: string = localize('createOperator.AlertNameColumnLabel', 'Alert name');
|
||||
private static readonly AlertEmailColumnLabel: string = localize('createOperator.AlertEmailColumnLabel', 'E-mail');
|
||||
private static readonly AlertPagerColumnLabel: string = localize('createOperator.AlertPagerColumnLabel', 'Pager');
|
||||
|
||||
// UI Components
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
// General tab controls
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private enabledCheckBox: sqlops.CheckBoxComponent;
|
||||
private emailNameTextBox: sqlops.InputBoxComponent;
|
||||
private pagerEmailNameTextBox: sqlops.InputBoxComponent;
|
||||
private pagerMondayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerTuesdayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerWednesdayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerThursdayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerFridayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerSaturdayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerSundayCheckBox: sqlops.CheckBoxComponent;
|
||||
private weekdayPagerStartTimeInput: sqlops.InputBoxComponent;
|
||||
private weekdayPagerEndTimeInput: sqlops.InputBoxComponent;
|
||||
private saturdayPagerStartTimeInput: sqlops.InputBoxComponent;
|
||||
private saturdayPagerEndTimeInput: sqlops.InputBoxComponent;
|
||||
private sundayPagerStartTimeInput: sqlops.InputBoxComponent;
|
||||
private sundayPagerEndTimeInput: sqlops.InputBoxComponent;
|
||||
|
||||
// Notification tab controls
|
||||
private alertsTable: sqlops.TableComponent;
|
||||
|
||||
constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
|
||||
super(
|
||||
ownerUri,
|
||||
new OperatorData(ownerUri, operatorInfo),
|
||||
operatorInfo ? OperatorDialog.EditDialogTitle : OperatorDialog.CreateDialogTitle);
|
||||
}
|
||||
|
||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(OperatorDialog.GeneralTabText);
|
||||
this.notificationsTab = sqlops.window.modelviewdialog.createTab(OperatorDialog.NotificationsTabText);
|
||||
|
||||
this.initializeGeneralTab();
|
||||
this.initializeNotificationTab();
|
||||
|
||||
this.dialog.content = [this.generalTab, this.notificationsTab];
|
||||
}
|
||||
|
||||
private initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
|
||||
this.nameTextBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
this.enabledCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.EnabledCheckboxLabel
|
||||
}).component();
|
||||
this.enabledCheckBox.checked = true;
|
||||
this.emailNameTextBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
this.pagerEmailNameTextBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
this.enabledCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.EnabledCheckboxLabel
|
||||
}).component();
|
||||
|
||||
this.pagerMondayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerMondayCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.pagerMondayCheckBox.onChanged(() => {
|
||||
if (this.pagerMondayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerTuesdayCheckBox.checked && !this.pagerWednesdayCheckBox.checked &&
|
||||
!this.pagerThursdayCheckBox.checked && !this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pagerTuesdayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerTuesdayCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
|
||||
this.pagerTuesdayCheckBox.onChanged(() => {
|
||||
if (this.pagerTuesdayCheckBox .checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerMondayCheckBox.checked && !this.pagerWednesdayCheckBox.checked &&
|
||||
!this.pagerThursdayCheckBox.checked && !this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pagerWednesdayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerWednesdayCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.pagerWednesdayCheckBox.onChanged(() => {
|
||||
if (this.pagerWednesdayCheckBox .checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerMondayCheckBox.checked && !this.pagerTuesdayCheckBox.checked &&
|
||||
!this.pagerThursdayCheckBox.checked && !this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pagerThursdayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerThursdayCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.pagerThursdayCheckBox.onChanged(() => {
|
||||
if (this.pagerThursdayCheckBox .checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerMondayCheckBox.checked && !this.pagerWednesdayCheckBox.checked &&
|
||||
!this.pagerTuesdayCheckBox.checked && !this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.weekdayPagerStartTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '08:00:00',
|
||||
}).component();
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
let weekdayStartInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.weekdayPagerStartTimeInput,
|
||||
title: OperatorDialog.WorkdayBeginLabel
|
||||
}]).component();
|
||||
|
||||
this.weekdayPagerEndTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '06:00:00'
|
||||
}).component();
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
let weekdayEndInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.weekdayPagerEndTimeInput,
|
||||
title: OperatorDialog.WorkdayEndLabel
|
||||
}]).component();
|
||||
|
||||
this.pagerFridayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerFridayCheckBoxLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
this.pagerFridayCheckBox.onChanged(() => {
|
||||
if (this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerMondayCheckBox.checked && !this.pagerWednesdayCheckBox.checked &&
|
||||
!this.pagerThursdayCheckBox.checked && !this.pagerTuesdayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let pagerFridayCheckboxContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'baseline',
|
||||
width: '100%'
|
||||
}).withItems([this.pagerFridayCheckBox, weekdayStartInputContainer, weekdayEndInputContainer])
|
||||
.component();
|
||||
|
||||
this.pagerSaturdayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerSaturdayCheckBoxLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.pagerSaturdayCheckBox.onChanged(() => {
|
||||
if (this.pagerSaturdayCheckBox.checked) {
|
||||
this.saturdayPagerStartTimeInput.enabled = true;
|
||||
this.saturdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
this.saturdayPagerStartTimeInput.enabled = false;
|
||||
this.saturdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.saturdayPagerStartTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '08:00:00'
|
||||
}).component();
|
||||
this.saturdayPagerStartTimeInput.enabled = false;
|
||||
let saturdayStartInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.saturdayPagerStartTimeInput,
|
||||
title: OperatorDialog.WorkdayBeginLabel
|
||||
}]).component();
|
||||
|
||||
this.saturdayPagerEndTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '06:00:00'
|
||||
}).component();
|
||||
this.saturdayPagerEndTimeInput.enabled = false;
|
||||
let saturdayEndInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.saturdayPagerEndTimeInput,
|
||||
title: OperatorDialog.WorkdayEndLabel
|
||||
}]).component();
|
||||
|
||||
let pagerSaturdayCheckboxContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'baseline'
|
||||
}).withItems([this.pagerSaturdayCheckBox, saturdayStartInputContainer, saturdayEndInputContainer])
|
||||
.component();
|
||||
|
||||
this.pagerSundayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerSundayCheckBoxLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.pagerSundayCheckBox.onChanged(() => {
|
||||
if (this.pagerSundayCheckBox.checked) {
|
||||
this.sundayPagerStartTimeInput.enabled = true;
|
||||
this.sundayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
this.sundayPagerStartTimeInput.enabled = false;
|
||||
this.sundayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.sundayPagerStartTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '08:00:00'
|
||||
}).component();
|
||||
this.sundayPagerStartTimeInput.enabled = false;
|
||||
let sundayStartInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.sundayPagerStartTimeInput,
|
||||
title: OperatorDialog.WorkdayBeginLabel
|
||||
}]).component();
|
||||
|
||||
this.sundayPagerEndTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '06:00:00'
|
||||
}).component();
|
||||
this.sundayPagerEndTimeInput.enabled = false;
|
||||
let sundayEndInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.sundayPagerEndTimeInput,
|
||||
title: OperatorDialog.WorkdayEndLabel
|
||||
}]).component();
|
||||
|
||||
let pagerSundayCheckboxContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'baseline'
|
||||
}).withItems([this.pagerSundayCheckBox, sundayStartInputContainer, sundayEndInputContainer])
|
||||
.component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: OperatorDialog.NameLabel
|
||||
}, {
|
||||
component: this.enabledCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.emailNameTextBox,
|
||||
title: OperatorDialog.EmailNameTextLabel
|
||||
}, {
|
||||
component: this.pagerEmailNameTextBox,
|
||||
title: OperatorDialog.PagerEmailNameTextLabel
|
||||
}, {
|
||||
components: [{
|
||||
component: this.pagerMondayCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.pagerTuesdayCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.pagerWednesdayCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.pagerThursdayCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: pagerFridayCheckboxContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: pagerSaturdayCheckboxContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: pagerSundayCheckboxContainer,
|
||||
title: ''
|
||||
}] ,
|
||||
title: OperatorDialog.PagerDutyScheduleLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeNotificationTab() {
|
||||
this.notificationsTab.registerContent(async view => {
|
||||
|
||||
this.alertsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
OperatorDialog.AlertNameColumnLabel,
|
||||
OperatorDialog.AlertEmailColumnLabel,
|
||||
OperatorDialog.AlertPagerColumnLabel
|
||||
],
|
||||
data: [],
|
||||
height: 500
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.alertsTable,
|
||||
title: OperatorDialog.AlertsTableLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
protected updateModel() {
|
||||
this.model.name = this.nameTextBox.value;
|
||||
this.model.enabled = this.enabledCheckBox.checked;
|
||||
this.model.emailAddress = this.emailNameTextBox.value;
|
||||
this.model.pagerAddress = this.pagerEmailNameTextBox.value;
|
||||
this.model.weekdayPagerStartTime = this.weekdayPagerStartTimeInput.value;
|
||||
this.model.weekdayPagerEndTime = this.weekdayPagerEndTimeInput.value;
|
||||
this.model.saturdayPagerStartTime = this.saturdayPagerStartTimeInput.value;
|
||||
this.model.saturdayPagerEndTime = this.saturdayPagerEndTimeInput.value;
|
||||
this.model.sundayPagerStartTime = this.sundayPagerStartTimeInput.value;
|
||||
this.model.sundayPagerEndTime = this.sundayPagerEndTimeInput.value;
|
||||
}
|
||||
}
|
||||
95
extensions/agent/src/dialogs/pickScheduleDialog.ts
Normal file
95
extensions/agent/src/dialogs/pickScheduleDialog.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { PickScheduleData } from '../data/pickScheduleData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class PickScheduleDialog {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
private readonly DialogTitle: string = localize('pickSchedule.jobSchedules', 'Job Schedules');
|
||||
private readonly OkButtonText: string = localize('pickSchedule.ok', 'OK');
|
||||
private readonly CancelButtonText: string = localize('pickSchedule.cancel', 'Cancel');
|
||||
private readonly ScheduleNameLabelText: string = localize('pickSchedule.scheduleName', 'Schedule Name');
|
||||
private readonly SchedulesLabelText: string = localize('pickSchedule.schedules', 'Schedules');
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private schedulesTable: sqlops.TableComponent;
|
||||
|
||||
private model: PickScheduleData;
|
||||
|
||||
private _onSuccess: vscode.EventEmitter<PickScheduleData> = new vscode.EventEmitter<PickScheduleData>();
|
||||
public readonly onSuccess: vscode.Event<PickScheduleData> = this._onSuccess.event;
|
||||
|
||||
constructor(ownerUri: string) {
|
||||
this.model = new PickScheduleData(ownerUri);
|
||||
}
|
||||
|
||||
public async showDialog() {
|
||||
await this.model.initialize();
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.initializeContent();
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
this.dialog.okButton.label = this.OkButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private initializeContent() {
|
||||
this.dialog.registerContent(async view => {
|
||||
this.schedulesTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.ScheduleNameLabelText
|
||||
],
|
||||
data: [],
|
||||
height: '80em',
|
||||
width: '40em'
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
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.name ];
|
||||
}
|
||||
this.schedulesTable.data = data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
this.updateModel();
|
||||
await this.model.save();
|
||||
this._onSuccess.fire(this.model);
|
||||
}
|
||||
|
||||
private async cancel() {
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
let selectedRows = this.schedulesTable.selectedRows;
|
||||
if (selectedRows && selectedRows.length > 0) {
|
||||
let selectedRow = selectedRows[0];
|
||||
this.model.selectedSchedule = this.model.schedules[selectedRow];
|
||||
}
|
||||
}
|
||||
}
|
||||
219
extensions/agent/src/dialogs/proxyDialog.ts
Normal file
219
extensions/agent/src/dialogs/proxyDialog.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
import { ProxyData } from '../data/proxyData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ProxyDialog extends AgentDialog<ProxyData> {
|
||||
|
||||
// Top level
|
||||
private static readonly CreateDialogTitle: string = localize('createProxy.createProxy', 'Create Proxy');
|
||||
private static readonly EditDialogTitle: string = localize('createProxy.editProxy', 'Edit Proxy');
|
||||
private static readonly GeneralTabText: string = localize('createProxy.General', 'General');
|
||||
|
||||
// General tab strings
|
||||
private static readonly ProxyNameTextBoxLabel: string = localize('createProxy.ProxyName', 'Proxy name');
|
||||
private static readonly CredentialNameTextBoxLabel: string = localize('createProxy.CredentialName', 'Credential name');
|
||||
private static readonly DescriptionTextBoxLabel: string = localize('createProxy.Description', 'Description');
|
||||
private static readonly SubsystemLabel: string = localize('createProxy.SubsystemName', 'Subsystem');
|
||||
private static readonly OperatingSystemLabel: string = localize('createProxy.OperatingSystem', 'Operating system (CmdExec)');
|
||||
private static readonly ReplicationSnapshotLabel: string = localize('createProxy.ReplicationSnapshot', 'Replication Snapshot');
|
||||
private static readonly ReplicationTransactionLogLabel: string = localize('createProxy.ReplicationTransactionLog', 'Replication Transaction-Log Reader');
|
||||
private static readonly ReplicationDistributorLabel: string = localize('createProxy.ReplicationDistributor', 'Replication Distributor');
|
||||
private static readonly ReplicationMergeLabel: string = localize('createProxy.ReplicationMerge', 'Replication Merge');
|
||||
private static readonly ReplicationQueueReaderLabel: string = localize('createProxy.ReplicationQueueReader', 'Replication Queue Reader');
|
||||
private static readonly SSASQueryLabel: string = localize('createProxy.SSASQueryLabel', 'SQL Server Analysis Services Query');
|
||||
private static readonly SSASCommandLabel: string = localize('createProxy.SSASCommandLabel', 'SQL Server Analysis Services Command');
|
||||
private static readonly SSISPackageLabel: string = localize('createProxy.SSISPackage', 'SQL Server Integration Services Package');
|
||||
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
|
||||
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
|
||||
|
||||
// UI Components
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
// General tab controls
|
||||
private proxyNameTextBox: sqlops.InputBoxComponent;
|
||||
private credentialNameDropDown: sqlops.DropDownComponent;
|
||||
private descriptionTextBox: sqlops.InputBoxComponent;
|
||||
private subsystemCheckBox: sqlops.CheckBoxComponent;
|
||||
private operatingSystemCheckBox: sqlops.CheckBoxComponent;
|
||||
private replicationSnapshotCheckBox: sqlops.CheckBoxComponent;
|
||||
private replicationTransactionLogCheckBox: sqlops.CheckBoxComponent;
|
||||
private replicationDistributorCheckBox: sqlops.CheckBoxComponent;
|
||||
private replicationMergeCheckbox: sqlops.CheckBoxComponent;
|
||||
private replicationQueueReaderCheckbox: sqlops.CheckBoxComponent;
|
||||
private sqlQueryCheckBox: sqlops.CheckBoxComponent;
|
||||
private sqlCommandCheckBox: sqlops.CheckBoxComponent;
|
||||
private sqlIntegrationServicesPackageCheckbox: sqlops.CheckBoxComponent;
|
||||
private powershellCheckBox: sqlops.CheckBoxComponent;
|
||||
|
||||
private credentials: sqlops.CredentialInfo[];
|
||||
|
||||
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
|
||||
super(
|
||||
ownerUri,
|
||||
new ProxyData(ownerUri, proxyInfo),
|
||||
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(ProxyDialog.GeneralTabText);
|
||||
|
||||
|
||||
this.initializeGeneralTab();
|
||||
|
||||
this.dialog.content = [this.generalTab];
|
||||
}
|
||||
|
||||
private initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
|
||||
this.proxyNameTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({width: 420})
|
||||
.component();
|
||||
|
||||
this.credentialNameDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
width: 432,
|
||||
value: '',
|
||||
editable: true,
|
||||
values: this.credentials.length > 0 ? this.credentials.map(c => c.name) : ['']
|
||||
})
|
||||
.component();
|
||||
|
||||
this.descriptionTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
width: 420,
|
||||
multiline: true,
|
||||
height: 300
|
||||
})
|
||||
.component();
|
||||
|
||||
this.subsystemCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.SubsystemLabel
|
||||
}).component();
|
||||
|
||||
this.subsystemCheckBox.onChanged(() => {
|
||||
if (this.subsystemCheckBox.checked) {
|
||||
this.operatingSystemCheckBox.checked = true;
|
||||
this.replicationSnapshotCheckBox.checked = true;
|
||||
this.replicationTransactionLogCheckBox.checked = true;
|
||||
this.replicationDistributorCheckBox.checked = true;
|
||||
this.replicationMergeCheckbox.checked = true;
|
||||
this.replicationQueueReaderCheckbox.checked = true;
|
||||
this.sqlQueryCheckBox.checked = true;
|
||||
this.sqlCommandCheckBox.checked = true;
|
||||
this.sqlIntegrationServicesPackageCheckbox.checked = true;
|
||||
this.powershellCheckBox.checked = true;
|
||||
} else {
|
||||
this.operatingSystemCheckBox.checked = false;
|
||||
this.replicationSnapshotCheckBox.checked = false;
|
||||
this.replicationTransactionLogCheckBox.checked = false;
|
||||
this.replicationDistributorCheckBox.checked = false;
|
||||
this.replicationMergeCheckbox.checked = false;
|
||||
this.replicationQueueReaderCheckbox.checked = false;
|
||||
this.sqlQueryCheckBox.checked = false;
|
||||
this.sqlCommandCheckBox.checked = false;
|
||||
this.sqlIntegrationServicesPackageCheckbox.checked = false;
|
||||
this.powershellCheckBox.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.operatingSystemCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.OperatingSystemLabel
|
||||
}).component();
|
||||
|
||||
this.replicationSnapshotCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationSnapshotLabel
|
||||
}).component();
|
||||
|
||||
this.replicationTransactionLogCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationTransactionLogLabel
|
||||
}).component();
|
||||
|
||||
this.replicationDistributorCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationDistributorLabel
|
||||
}).component();
|
||||
|
||||
this.replicationMergeCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationMergeLabel
|
||||
}).component();
|
||||
|
||||
this.replicationQueueReaderCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationQueueReaderLabel
|
||||
}).component();
|
||||
|
||||
this.sqlQueryCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.SSASQueryLabel
|
||||
}).component();
|
||||
|
||||
this.sqlCommandCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.SSASCommandLabel
|
||||
}).component();
|
||||
|
||||
this.sqlIntegrationServicesPackageCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.SSISPackageLabel
|
||||
}).component();
|
||||
|
||||
this.powershellCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.PowerShellLabel
|
||||
}).component();
|
||||
|
||||
let checkBoxContainer = view.modelBuilder.groupContainer()
|
||||
.withItems([this.operatingSystemCheckBox, this.replicationSnapshotCheckBox,
|
||||
this.replicationTransactionLogCheckBox, this.replicationDistributorCheckBox, this.replicationMergeCheckbox,
|
||||
this.replicationQueueReaderCheckbox, this.sqlQueryCheckBox, this.sqlCommandCheckBox, this.sqlIntegrationServicesPackageCheckbox,
|
||||
this.powershellCheckBox])
|
||||
.component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.proxyNameTextBox,
|
||||
title: ProxyDialog.ProxyNameTextBoxLabel
|
||||
}, {
|
||||
component: this.credentialNameDropDown,
|
||||
title: ProxyDialog.CredentialNameTextBoxLabel
|
||||
}, {
|
||||
component: this.descriptionTextBox,
|
||||
title: ProxyDialog.DescriptionTextBoxLabel
|
||||
}]).withLayout({ width: 420 }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
this.proxyNameTextBox.value = this.model.accountName;
|
||||
this.credentialNameDropDown.value = this.model.credentialName;
|
||||
this.descriptionTextBox.value = this.model.description;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected updateModel() {
|
||||
this.model.accountName = this.proxyNameTextBox.value;
|
||||
this.model.credentialName = this.credentialNameDropDown.value as string;
|
||||
this.model.credentialId = this.credentials.find(
|
||||
c => c.name === this.model.credentialName).id;
|
||||
this.model.credentialIdentity = this.credentials.find(
|
||||
c => c.name === this.model.credentialName).identity;
|
||||
this.model.description = this.descriptionTextBox.value;
|
||||
}
|
||||
}
|
||||
95
extensions/agent/src/dialogs/scheduleDialog.ts
Normal file
95
extensions/agent/src/dialogs/scheduleDialog.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { ScheduleData } from '../data/scheduleData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ScheduleDialog {
|
||||
|
||||
// Top level
|
||||
private readonly DialogTitle: string = localize('scheduleDialog.newSchedule', 'New Schedule');
|
||||
private readonly OkButtonText: string = localize('scheduleDialog.ok', 'OK');
|
||||
private readonly CancelButtonText: string = localize('scheduleDialog.cancel', 'Cancel');
|
||||
private readonly ScheduleNameText: string = localize('scheduleDialog.scheduleName', 'Schedule Name');
|
||||
private readonly SchedulesLabelText: string = localize('scheduleDialog.schedules', 'Schedules');
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private schedulesTable: sqlops.TableComponent;
|
||||
|
||||
private model: ScheduleData;
|
||||
|
||||
private _onSuccess: vscode.EventEmitter<ScheduleData> = new vscode.EventEmitter<ScheduleData>();
|
||||
public readonly onSuccess: vscode.Event<ScheduleData> = this._onSuccess.event;
|
||||
|
||||
constructor(ownerUri: string) {
|
||||
this.model = new ScheduleData(ownerUri);
|
||||
}
|
||||
|
||||
public async showDialog() {
|
||||
await this.model.initialize();
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.initializeContent();
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
this.dialog.okButton.label = this.OkButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private initializeContent() {
|
||||
this.dialog.registerContent(async view => {
|
||||
this.schedulesTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.ScheduleNameText
|
||||
],
|
||||
data: [],
|
||||
height: 600,
|
||||
width: 400
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
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.name ];
|
||||
}
|
||||
this.schedulesTable.data = data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
this.updateModel();
|
||||
await this.model.save();
|
||||
this._onSuccess.fire(this.model);
|
||||
}
|
||||
|
||||
private async cancel() {
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
let selectedRows = this.schedulesTable.selectedRows;
|
||||
if (selectedRows && selectedRows.length > 0) {
|
||||
let selectedRow = selectedRows[0];
|
||||
this.model.selectedSchedule = this.model.schedules[selectedRow];
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/agent/src/interfaces.ts
Normal file
17
extensions/agent/src/interfaces.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 enum AgentDialogMode {
|
||||
CREATE = 1,
|
||||
EDIT = 2,
|
||||
VIEW = 3
|
||||
}
|
||||
|
||||
export interface IAgentDialogData {
|
||||
dialogMode: AgentDialogMode;
|
||||
initialize(): void;
|
||||
save(): void;
|
||||
}
|
||||
@@ -6,12 +6,10 @@
|
||||
|
||||
import vscode = require('vscode');
|
||||
import { MainController } from './mainController';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
export let controller: MainController;
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
let apiWrapper = new ApiWrapper();
|
||||
controller = new MainController(context, apiWrapper);
|
||||
controller = new MainController(context);
|
||||
controller.activate();
|
||||
}
|
||||
|
||||
72
extensions/agent/src/mainController.ts
Normal file
72
extensions/agent/src/mainController.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { AlertDialog } from './dialogs/alertDialog';
|
||||
import { JobDialog } from './dialogs/jobDialog';
|
||||
import { OperatorDialog } from './dialogs/operatorDialog';
|
||||
import { ProxyDialog } from './dialogs/proxyDialog';
|
||||
import { JobStepDialog } from './dialogs/jobStepDialog';
|
||||
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public static showNotYetImplemented(): void {
|
||||
vscode.window.showInformationMessage(
|
||||
localize('mainController.notImplemented', "This feature is under development. Check-out the latest insiders build if you'd like to try out the most recent changes!"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the extension
|
||||
*/
|
||||
public activate(): void {
|
||||
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
|
||||
let dialog = new JobDialog(ownerUri, jobInfo);
|
||||
dialog.openDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, jobId: string, server: string, stepId: number) => {
|
||||
let dialog = new JobStepDialog(ownerUri, jobId, server, stepId);
|
||||
dialog.openNewStepDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string) => {
|
||||
let dialog = new PickScheduleDialog(ownerUri);
|
||||
dialog.showDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => {
|
||||
let dialog = new AlertDialog(ownerUri, alertInfo, jobs);
|
||||
dialog.openDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
|
||||
let dialog = new OperatorDialog(ownerUri, operatorInfo);
|
||||
dialog.openDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
|
||||
//@TODO: reenable create proxy after snapping July release (7/14/18)
|
||||
// let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
|
||||
// dialog.openDialog();
|
||||
MainController.showNotYetImplemented();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
}
|
||||
21
extensions/agent/src/test/agent.test.ts
Normal file
21
extensions/agent/src/test/agent.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import * as vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { JobData } from '../data/jobData';
|
||||
import { TestAgentService } from './testAgentService';
|
||||
|
||||
const testOwnerUri = 'agent://testuri';
|
||||
|
||||
suite('Agent extension', () => {
|
||||
test('Create Job Data', async () => {
|
||||
let testAgentService = new TestAgentService();
|
||||
let data = new JobData(testOwnerUri, undefined, testAgentService);
|
||||
data.save();
|
||||
});
|
||||
});
|
||||
110
extensions/agent/src/test/testAgentService.ts
Normal file
110
extensions/agent/src/test/testAgentService.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export class TestAgentService implements sqlops.AgentServicesProvider {
|
||||
handle?: number;
|
||||
readonly providerId: string = 'Test Provider';
|
||||
|
||||
// Job management methods
|
||||
getJobs(ownerUri: string): Thenable<sqlops.AgentJobsResult> {
|
||||
return undefined;
|
||||
}
|
||||
getJobHistory(ownerUri: string, jobId: string): Thenable<sqlops.AgentJobHistoryResult> {
|
||||
return undefined;
|
||||
}
|
||||
jobAction(ownerUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
createJob(ownerUri: string, jobInfo: sqlops.AgentJobInfo): Thenable<sqlops.CreateAgentJobResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateJob(ownerUri: string, originalJobName: string, jobInfo: sqlops.AgentJobInfo): Thenable<sqlops.UpdateAgentJobResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteJob(ownerUri: string, jobInfo: sqlops.AgentJobInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
getJobDefaults(ownerUri: string): Thenable<sqlops.AgentJobDefaultsResult> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Job Step management methods
|
||||
createJobStep(ownerUri: string, jobInfo: sqlops.AgentJobStepInfo): Thenable<sqlops.CreateAgentJobStepResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateJobStep(ownerUri: string, originalJobStepName: string, jobInfo: sqlops.AgentJobStepInfo): Thenable<sqlops.UpdateAgentJobStepResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteJobStep(ownerUri: string, jobInfo: sqlops.AgentJobStepInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Alert management methods
|
||||
getAlerts(ownerUri: string): Thenable<sqlops.AgentAlertsResult> {
|
||||
return undefined;
|
||||
}
|
||||
createAlert(ownerUri: string, alertInfo: sqlops.AgentAlertInfo): Thenable<sqlops.CreateAgentAlertResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateAlert(ownerUri: string, originalAlertName: string, alertInfo: sqlops.AgentAlertInfo): Thenable<sqlops.UpdateAgentAlertResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteAlert(ownerUri: string, alertInfo: sqlops.AgentAlertInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Operator management methods
|
||||
getOperators(ownerUri: string): Thenable<sqlops.AgentOperatorsResult> {
|
||||
return undefined;
|
||||
}
|
||||
createOperator(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo): Thenable<sqlops.CreateAgentOperatorResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateOperator(ownerUri: string, originalOperatorName: string, operatorInfo: sqlops.AgentOperatorInfo): Thenable<sqlops.UpdateAgentOperatorResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteOperator(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Proxy management methods
|
||||
getProxies(ownerUri: string): Thenable<sqlops.AgentProxiesResult> {
|
||||
return undefined;
|
||||
}
|
||||
createProxy(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo): Thenable<sqlops.CreateAgentOperatorResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateProxy(ownerUri: string, originalProxyName: string, proxyInfo: sqlops.AgentProxyInfo): Thenable<sqlops.UpdateAgentOperatorResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteProxy(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Agent Credential method
|
||||
getCredentials(ownerUri: string): Thenable<sqlops.GetCredentialsResult> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Job Schedule management methods
|
||||
getJobSchedules(ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> {
|
||||
return undefined;
|
||||
}
|
||||
createJobSchedule(ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.CreateAgentJobScheduleResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateJobSchedule(ownerUri: string, originalScheduleName: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.UpdateAgentJobScheduleResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteJobSchedule(ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerOnUpdated(handler: () => any): void {
|
||||
}
|
||||
}
|
||||
9
extensions/agent/src/typings/ref.d.ts
vendored
Normal file
9
extensions/agent/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* 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/sqlops.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,4 +2,4 @@
|
||||
|
||||
See [documentation](https://code.visualstudio.com/docs/editor/versioncontrol#_merge-conflicts).
|
||||
|
||||
**Notice** This is a an extension that is bundled with Visual Studio Code.
|
||||
**Notice** This is a an extension that is bundled with SQL Operations Studio.
|
||||
|
||||
@@ -658,9 +658,9 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.8.2",
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.0",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
"GO",
|
||||
"-- Create the new database if it does not exist already",
|
||||
"IF NOT EXISTS (",
|
||||
"\tSELECT name",
|
||||
"\tSELECT [name]",
|
||||
"\t\tFROM sys.databases",
|
||||
"\t\tWHERE name = N'${1:DatabaseName}'",
|
||||
"\t\tWHERE [name] = N'${1:DatabaseName}'",
|
||||
")",
|
||||
"CREATE DATABASE ${1:DatabaseName}",
|
||||
"GO"
|
||||
@@ -29,9 +29,9 @@
|
||||
"-- ALTER DATABASE ${1:DatabaseName} SET SINGLE_USER WITH ROLLBACK IMMEDIATE;",
|
||||
"-- Drop the database if it exists",
|
||||
"IF EXISTS (",
|
||||
" SELECT name",
|
||||
" SELECT [name]",
|
||||
" FROM sys.databases",
|
||||
" WHERE name = N'${1:DatabaseName}'",
|
||||
" WHERE [name] = N'${1:DatabaseName}'",
|
||||
")",
|
||||
"DROP DATABASE ${1:DatabaseName}",
|
||||
"GO"
|
||||
@@ -42,38 +42,33 @@
|
||||
"Create a new Table": {
|
||||
"prefix": "sqlCreateTable",
|
||||
"body": [
|
||||
"-- Create a new table called '${1:TableName}' in schema '${2:SchemaName}'",
|
||||
"-- Create a new table called '[${1:TableName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"-- Drop the table if it already exists",
|
||||
"IF OBJECT_ID('${2:SchemaName}.${1:TableName}', 'U') IS NOT NULL",
|
||||
"DROP TABLE ${2:SchemaName}.${1:TableName}",
|
||||
"IF OBJECT_ID('[${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]', 'U') IS NOT NULL",
|
||||
"DROP TABLE [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"GO",
|
||||
"-- Create the table in the specified schema",
|
||||
"CREATE TABLE ${2:SchemaName}.${1:TableName}",
|
||||
"-- Create the table in the specified database and schema",
|
||||
"CREATE TABLE [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"(",
|
||||
"\t${1:TableName}Id INT NOT NULL PRIMARY KEY, -- primary key column",
|
||||
"\t$3Column1 [NVARCHAR](50) NOT NULL,",
|
||||
"\t$4Column2 [NVARCHAR](50) NOT NULL",
|
||||
"\t-- specify more columns here",
|
||||
"\t[${4:ColumnName}]Id INT NOT NULL PRIMARY KEY, -- Primary Key column",
|
||||
"\t[${5:ColumnName1}] [NVARCHAR](50) NOT NULL,",
|
||||
"\t[${6:ColumnName2}] [NVARCHAR](50) NOT NULL",
|
||||
"\t-- Specify more columns here",
|
||||
");",
|
||||
"GO"
|
||||
],
|
||||
"description": "Create a new Table"
|
||||
},
|
||||
|
||||
|
||||
"Drop a Table": {
|
||||
"prefix": "sqlDropTable",
|
||||
"body": [
|
||||
"-- Drop the table '${1:TableName}' in schema '${2:SchemaName}'",
|
||||
"IF EXISTS (",
|
||||
"\tSELECT *",
|
||||
"\t\tFROM sys.tables",
|
||||
"\t\tJOIN sys.schemas",
|
||||
"\t\t\tON sys.tables.schema_id = sys.schemas.schema_id",
|
||||
"\tWHERE sys.schemas.name = N'${2:SchemaName}'",
|
||||
"\t\tAND sys.tables.name = N'${1:TableName}'",
|
||||
")",
|
||||
"\tDROP TABLE ${2:SchemaName}.${1:TableName}",
|
||||
"GO"
|
||||
"-- Drop a table called '${3:TableName}' in schema '${2:SchemaName}' in Database '${1:DatabaseName}'",
|
||||
"-- Drop the table if it already exists",
|
||||
"IF OBJECT_ID('[${1:DatabaseName}].[${2:SchemaName}].[${3:TableName}]', 'U') IS NOT NULL",
|
||||
"DROP TABLE [${1:DatabaseName}].[${2:SchemaName}].[${3:TableName}]",
|
||||
"GO"
|
||||
],
|
||||
"description": "Drop a Table"
|
||||
},
|
||||
@@ -81,9 +76,9 @@
|
||||
"Add a new column to a Table": {
|
||||
"prefix": "sqlAddColumn",
|
||||
"body": [
|
||||
"-- Add a new column '${1:NewColumnName}' to table '${2:TableName}' in schema '${3:SchemaName}'",
|
||||
"ALTER TABLE ${3:SchemaName}.${2:TableName}",
|
||||
"\tADD ${1:NewColumnName} /*new_column_name*/ int /*new_column_datatype*/ NULL /*new_column_nullability*/",
|
||||
"-- Add a new column '[${1:NewColumnName}]' to table '[${2:TableName}]' in schema '[${3:SchemaName}]' in database '[${4:DatabaseName}]'",
|
||||
"ALTER TABLE [${4:DatabaseName}].[${3:SchemaName}].[${2:TableName}]",
|
||||
"\tADD [${1:NewColumnName}] /*new_column_name*/ ${5:int} /*new_column_datatype*/ ${6:NULL} /*new_column_nullability*/",
|
||||
"GO"
|
||||
],
|
||||
"description": "Add a new column to a Table"
|
||||
@@ -92,9 +87,9 @@
|
||||
"Drop a column from a Table": {
|
||||
"prefix": "sqlDropColumn",
|
||||
"body": [
|
||||
"-- Drop '${1:ColumnName}' from table '${2:TableName}' in schema '${3:SchemaName}'",
|
||||
"ALTER TABLE ${3:SchemaName}.${2:TableName}",
|
||||
"\tDROP COLUMN ${1:ColumnName}",
|
||||
"-- Drop '[${1:ColumnName}]' from table '[${2:TableName}]' in schema '[${3:SchemaName}]' in database '[${4:DatabaseName}]'",
|
||||
"ALTER TABLE [${4:DatabaseName}].[${3:SchemaName}].[${2:TableName}]",
|
||||
"\tDROP COLUMN [${1:ColumnName}]",
|
||||
"GO"
|
||||
],
|
||||
"description": "Add a new column to a Table"
|
||||
@@ -103,9 +98,9 @@
|
||||
"Select rows from a Table or a View": {
|
||||
"prefix": "sqlSelect",
|
||||
"body": [
|
||||
"-- Select rows from a Table or View '${1:TableOrViewName}' in schema '${2:SchemaName}'",
|
||||
"SELECT * FROM ${2:SchemaName}.${1:TableOrViewName}",
|
||||
"WHERE $3\t/* add search conditions here */",
|
||||
"-- Select rows from a Table or View '[${1:TableOrViewName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"SELECT * FROM [${3:DatabaseName}].[${2:SchemaName}].[${1:TableOrViewName}]",
|
||||
"WHERE ${4:/* add search conditions here */}",
|
||||
"GO"
|
||||
],
|
||||
"description": "Select rows from a Table or a View"
|
||||
@@ -114,19 +109,19 @@
|
||||
"Insert rows into a Table": {
|
||||
"prefix": "sqlInsertRows",
|
||||
"body": [
|
||||
"-- Insert rows into table '${1:TableName}'",
|
||||
"INSERT INTO ${1:TableName}",
|
||||
"( -- columns to insert data into",
|
||||
" $2[Column1], [Column2], [Column3]",
|
||||
"-- Insert rows into table '${1:TableName}' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"INSERT INTO [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"( -- Columns to insert data into",
|
||||
" ${4:[ColumnName1], [ColumnName2], [ColumnName3]}",
|
||||
")",
|
||||
"VALUES",
|
||||
"( -- first row: values for the columns in the list above",
|
||||
" $3Column1_Value, Column2_Value, Column3_Value",
|
||||
"( -- First row: values for the columns in the list above",
|
||||
" ${5:ColumnValue1, ColumnValue2, ColumnValue3}",
|
||||
"),",
|
||||
"( -- second row: values for the columns in the list above",
|
||||
" $4Column1_Value, Column2_Value, Column3_Value",
|
||||
"( -- Second row: values for the columns in the list above",
|
||||
" ${6:ColumnValue1, ColumnValue2, ColumnValue3}",
|
||||
")",
|
||||
"-- add more rows here",
|
||||
"-- Add more rows here",
|
||||
"GO"
|
||||
],
|
||||
"description": "Insert rows into a Table"
|
||||
@@ -135,9 +130,9 @@
|
||||
"Delete rows from a Table": {
|
||||
"prefix": "sqlDeleteRows",
|
||||
"body": [
|
||||
"-- Delete rows from table '${1:TableName}'",
|
||||
"DELETE FROM ${1:TableName}",
|
||||
"WHERE $2\t/* add search conditions here */",
|
||||
"-- Delete rows from table '[${1:TableName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"DELETE FROM [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"WHERE ${4:/* add search conditions here */}",
|
||||
"GO"
|
||||
],
|
||||
"description": "Delete rows from a Table"
|
||||
@@ -146,13 +141,13 @@
|
||||
"Update rows in a Table": {
|
||||
"prefix": "sqlUpdateRows",
|
||||
"body": [
|
||||
"-- Update rows in table '${1:TableName}'",
|
||||
"UPDATE ${1:TableName}",
|
||||
"-- Update rows in table '[${1:TableName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"UPDATE [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"SET",
|
||||
"\t$2[Colum1] = Colum1_Value,",
|
||||
"\t$3[Colum2] = Colum2_Value",
|
||||
"\t-- add more columns and values here",
|
||||
"WHERE $4\t/* add search conditions here */",
|
||||
"\t[${4:ColumnName1}] = ${5:ColumnValue1},",
|
||||
"\t[${6:ColumnName2}] = ${7:ColumnValue2}",
|
||||
"\t-- Add more columns and values here",
|
||||
"WHERE ${8:/* add search conditions here */}",
|
||||
"GO"
|
||||
],
|
||||
"description": "Update rows in a Table"
|
||||
@@ -207,8 +202,8 @@
|
||||
"prefix": "sqlListTablesAndViews",
|
||||
"body": [
|
||||
"-- Get a list of tables and views in the current database",
|
||||
"SELECT table_catalog [database], table_schema [schema], table_name name, table_type type",
|
||||
"FROM information_schema.tables",
|
||||
"SELECT table_catalog [database], table_schema [schema], table_name [name], table_type [type]",
|
||||
"FROM INFORMATION_SCHEMA.TABLES",
|
||||
"GO"
|
||||
],
|
||||
"description": "List tables and vies in the current database"
|
||||
@@ -218,7 +213,7 @@
|
||||
"prefix": "sqlListDatabases",
|
||||
"body": [
|
||||
"-- Get a list of databases",
|
||||
"SELECT name FROM sys.databases",
|
||||
"SELECT [name] FROM sys.databases",
|
||||
"GO"
|
||||
],
|
||||
"description": "List databases"
|
||||
@@ -232,8 +227,8 @@
|
||||
"\tTableName = tbl.table_schema + '.' + tbl.table_name, ",
|
||||
"\tColumnName = col.column_name, ",
|
||||
"\tColumnDataType = col.data_type",
|
||||
"FROM information_schema.tables tbl",
|
||||
"INNER JOIN information_schema.columns col ",
|
||||
"FROM INFORMATION_SCHEMA.TABLES tbl",
|
||||
"INNER JOIN INFORMATION_SCHEMA.COLUMNS col ",
|
||||
"\tON col.table_name = tbl.table_name",
|
||||
"\tAND col.table_schema = tbl.table_schema",
|
||||
"",
|
||||
@@ -247,20 +242,20 @@
|
||||
"prefix": "sqlCursor",
|
||||
"body": [
|
||||
"-- Declare a cursor for a Table or a View '${1:TableOrViewName}' in schema '${2:SchemaName}'",
|
||||
"DECLARE @Column1 NVARCHAR(50), @Column2 NVARCHAR(50)",
|
||||
"DECLARE @ColumnName1 NVARCHAR(50), @ColumnName2 NVARCHAR(50)",
|
||||
"",
|
||||
"DECLARE db_cursor CURSOR FOR",
|
||||
"SELECT Column1, Column2",
|
||||
"SELECT ColumnName1, Column2",
|
||||
"FROM $2.$1",
|
||||
"",
|
||||
"OPEN db_cursor",
|
||||
"FETCH NEXT FROM db_cursor INTO @Column1, @Column2",
|
||||
"FETCH NEXT FROM db_cursor INTO @ColumnName1, @ColumnName2",
|
||||
"",
|
||||
"WHILE @@FETCH_STATUS = 0",
|
||||
"BEGIN",
|
||||
"\t-- add instructions to be executed for every row",
|
||||
"\t$3",
|
||||
"\tFETCH NEXT FROM db_cursor INTO @Column1, @Column2",
|
||||
"\tFETCH NEXT FROM db_cursor INTO @ColumnName1, @ColumnName2",
|
||||
"END",
|
||||
"",
|
||||
"CLOSE db_cursor",
|
||||
@@ -303,5 +298,34 @@
|
||||
"GO"
|
||||
],
|
||||
"description": "Get Space Used by Tables"
|
||||
}
|
||||
},
|
||||
|
||||
"Create a new Index": {
|
||||
"prefix": "sqlCreateIndex",
|
||||
"body": [
|
||||
"-- Create a nonclustered index with or without a unique constraint",
|
||||
"-- Or create a clustered index on table '[${1:TableName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"CREATE ${5:/*UNIQUE or CLUSTERED*/} INDEX IX_${4:IndexName} ON [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}] ([${6:ColumnName1}] DESC /*Change sort order as needed*/",
|
||||
"GO"
|
||||
],
|
||||
"description": "Create a new Index"
|
||||
},
|
||||
|
||||
"Create a new Temporary Table": {
|
||||
"prefix": "sqlCreateTempTable",
|
||||
"body": [
|
||||
"-- Drop a temporary table called '#${1:TableName}'",
|
||||
"-- Drop the table if it already exists",
|
||||
"IF OBJECT_ID('tempDB..#${1:TableName}', 'U') IS NOT NULL",
|
||||
"DROP TABLE #${1:TableName}",
|
||||
"GO",
|
||||
"-- Create the temporary table from a physical table called '${4:TableName}' in schema '${3:SchemaName}' in database '${2:DatabaseName}'",
|
||||
"SELECT *",
|
||||
"INTO #${1:TableName}",
|
||||
"FROM [${2:DatabaseName}].[${3:[SchemaName}].[${4:TableName}]",
|
||||
"WHERE ${5:/* add search conditions here */}"
|
||||
],
|
||||
"description": "Create a new Temporary Table"
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.4.0-alpha.45",
|
||||
"version": "1.5.0-alpha.14",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.1.zip",
|
||||
@@ -16,4 +16,4 @@
|
||||
},
|
||||
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
|
||||
"executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class ContextProvider {
|
||||
public onDashboardOpen(e: sqlops.DashboardDocument): void {
|
||||
let iscloud: boolean;
|
||||
let edition: number;
|
||||
if (e.profile.providerName.toLowerCase() === 'mssql' && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
|
||||
if (e.profile.providerName.toLowerCase() === 'mssql' && !types.isUndefinedOrNull(e.serverInfo) && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
|
||||
if (isCloudEditions.some(i => i === e.serverInfo.engineEditionId)) {
|
||||
iscloud = true;
|
||||
} else {
|
||||
|
||||
@@ -64,6 +64,10 @@ export interface DeleteAgentJobParams {
|
||||
job: sqlops.AgentJobInfo;
|
||||
}
|
||||
|
||||
export interface AgentJobDefaultsParams {
|
||||
ownerUri: string;
|
||||
}
|
||||
|
||||
// Job Step management parameters
|
||||
export interface CreateAgentJobStepParams {
|
||||
ownerUri: string;
|
||||
@@ -144,6 +148,32 @@ export interface DeleteAgentProxyParams {
|
||||
proxy: sqlops.AgentProxyInfo;
|
||||
}
|
||||
|
||||
// Agent Credentials parameters
|
||||
export interface GetCredentialsParams {
|
||||
ownerUri: string;
|
||||
}
|
||||
|
||||
// Job Schedule management parameters
|
||||
export interface AgentJobScheduleParams {
|
||||
ownerUri: string;
|
||||
}
|
||||
|
||||
export interface CreateAgentJobScheduleParams {
|
||||
ownerUri: string;
|
||||
schedule: sqlops.AgentJobScheduleInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentJobScheduleParams {
|
||||
ownerUri: string;
|
||||
originalScheduleName: string;
|
||||
schedule: sqlops.AgentJobScheduleInfo;
|
||||
}
|
||||
|
||||
export interface DeleteAgentJobScheduleParams {
|
||||
ownerUri: string;
|
||||
schedule: sqlops.AgentJobScheduleInfo;
|
||||
}
|
||||
|
||||
// Agent Job management requests
|
||||
export namespace AgentJobsRequest {
|
||||
export const type = new RequestType<AgentJobsParams, sqlops.AgentJobsResult, void, void>('agent/jobs');
|
||||
@@ -169,6 +199,10 @@ export namespace DeleteAgentJobRequest {
|
||||
export const type = new RequestType<DeleteAgentJobParams, sqlops.ResultStatus, void, void>('agent/deletejob');
|
||||
}
|
||||
|
||||
export namespace AgentJobDefaultsRequest {
|
||||
export const type = new RequestType<AgentJobDefaultsParams, sqlops.AgentJobDefaultsResult, void, void>('agent/jobdefaults');
|
||||
}
|
||||
|
||||
// Job Step requests
|
||||
export namespace CreateAgentJobStepRequest {
|
||||
export const type = new RequestType<CreateAgentJobStepParams, sqlops.CreateAgentJobStepResult, void, void>('agent/createjobstep');
|
||||
@@ -233,4 +267,26 @@ export namespace DeleteAgentProxyRequest {
|
||||
export const type = new RequestType<DeleteAgentProxyParams, sqlops.ResultStatus, void, void>('agent/deleteproxy');
|
||||
}
|
||||
|
||||
// Agent Credentials request
|
||||
export namespace AgentCredentialsRequest {
|
||||
export const type = new RequestType<GetCredentialsParams, sqlops.GetCredentialsResult, void, void>('security/credentials');
|
||||
}
|
||||
|
||||
// Job Schedules requests
|
||||
export namespace AgentJobSchedulesRequest {
|
||||
export const type = new RequestType<AgentJobScheduleParams, sqlops.AgentJobSchedulesResult, void, void>('agent/schedules');
|
||||
}
|
||||
|
||||
export namespace CreateAgentJobScheduleRequest {
|
||||
export const type = new RequestType<CreateAgentJobScheduleParams, sqlops.CreateAgentJobScheduleResult, void, void>('agent/createschedule');
|
||||
}
|
||||
|
||||
export namespace UpdateAgentJobScheduleRequest {
|
||||
export const type = new RequestType<UpdateAgentJobScheduleParams, sqlops.UpdateAgentJobScheduleResult, void, void>('agent/updateschedule');
|
||||
}
|
||||
|
||||
export namespace DeleteAgentJobScheduleRequest {
|
||||
export const type = new RequestType<DeleteAgentJobScheduleParams, sqlops.ResultStatus, void, void>('agent/deleteschedule');
|
||||
}
|
||||
|
||||
// ------------------------------- < Agent Management > ------------------------------------
|
||||
|
||||
@@ -35,6 +35,8 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
contracts.AgentJobActionRequest.type
|
||||
];
|
||||
|
||||
private onUpdatedHandler: () => any;
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, AgentServicesFeature.messagesTypes);
|
||||
}
|
||||
@@ -53,6 +55,18 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
let self = this;
|
||||
|
||||
// On updated registration
|
||||
let registerOnUpdated = (handler: () => any): void => {
|
||||
self.onUpdatedHandler = handler;
|
||||
};
|
||||
|
||||
let fireOnUpdated = (): void => {
|
||||
if (self.onUpdatedHandler) {
|
||||
self.onUpdatedHandler();
|
||||
}
|
||||
};
|
||||
|
||||
// Job management methods
|
||||
let getJobs = (ownerUri: string): Thenable<sqlops.AgentJobsResult> => {
|
||||
@@ -96,7 +110,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentJobRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -112,7 +129,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentJobRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -126,6 +146,23 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
job: jobInfo
|
||||
};
|
||||
let requestType = contracts.DeleteAgentJobRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let getJobDefaults = (ownerUri: string): Thenable<sqlops.AgentJobDefaultsResult> => {
|
||||
let params: contracts.AgentJobDefaultsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
let requestType = contracts.AgentJobDefaultsRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
@@ -143,7 +180,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentJobStepRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -159,7 +199,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentJobStepRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -174,7 +217,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.DeleteAgentJobStepRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -204,7 +250,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentAlertRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -220,7 +269,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentAlertRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -235,7 +287,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.DeleteAgentAlertRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -265,7 +320,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentOperatorRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -281,7 +339,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentOperatorRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -296,7 +357,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.DeleteAgentOperatorRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -326,7 +390,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentProxyRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -342,7 +409,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentProxyRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -356,6 +426,24 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
proxy: proxyInfo
|
||||
};
|
||||
let requestType = contracts.DeleteAgentProxyRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Agent Credential Method
|
||||
let getCredentials = (ownerUri: string): Thenable<sqlops.GetCredentialsResult> => {
|
||||
let params: contracts.GetCredentialsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
let requestType = contracts.AgentCredentialsRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
@@ -365,6 +453,77 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Job Schedule management methods
|
||||
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
|
||||
let params: contracts.AgentJobScheduleParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
let requestType = contracts.AgentJobSchedulesRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let createJobSchedule = (ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.CreateAgentJobScheduleResult> => {
|
||||
let params: contracts.CreateAgentJobScheduleParams = {
|
||||
ownerUri: ownerUri,
|
||||
schedule: scheduleInfo
|
||||
};
|
||||
let requestType = contracts.CreateAgentJobScheduleRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let updateJobSchedule = (ownerUri: string, originalScheduleName: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.UpdateAgentJobScheduleResult> => {
|
||||
let params: contracts.UpdateAgentJobScheduleParams = {
|
||||
ownerUri: ownerUri,
|
||||
originalScheduleName: originalScheduleName,
|
||||
schedule: scheduleInfo
|
||||
};
|
||||
let requestType = contracts.UpdateAgentJobScheduleRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let deleteJobSchedule = (ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.ResultStatus> => {
|
||||
let params: contracts.DeleteAgentJobScheduleParams = {
|
||||
ownerUri: ownerUri,
|
||||
schedule: scheduleInfo
|
||||
};
|
||||
let requestType = contracts.DeleteAgentJobScheduleRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return sqlops.dataprotocol.registerAgentServicesProvider({
|
||||
providerId: client.providerId,
|
||||
getJobs,
|
||||
@@ -373,6 +532,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
createJob,
|
||||
updateJob,
|
||||
deleteJob,
|
||||
getJobDefaults,
|
||||
createJobStep,
|
||||
updateJobStep,
|
||||
deleteJobStep,
|
||||
@@ -387,7 +547,13 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
getProxies,
|
||||
createProxy,
|
||||
updateProxy,
|
||||
deleteProxy
|
||||
deleteProxy,
|
||||
getCredentials,
|
||||
getJobSchedules,
|
||||
createJobSchedule,
|
||||
updateJobSchedule,
|
||||
deleteJobSchedule,
|
||||
registerOnUpdated
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,22 +44,6 @@ export class Telemetry {
|
||||
private static platformInformation: PlatformInformation;
|
||||
private static disabled: boolean;
|
||||
|
||||
// Get the unique ID for the current user of the extension
|
||||
public static getUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
// Generate the user id if it has not been created already
|
||||
if (typeof this.userId === 'undefined') {
|
||||
let id = Utils.generateUserId();
|
||||
id.then(newId => {
|
||||
this.userId = newId;
|
||||
resolve(this.userId);
|
||||
});
|
||||
} else {
|
||||
resolve(this.userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getPlatformInformation(): Promise<PlatformInformation> {
|
||||
if (this.platformInformation) {
|
||||
return Promise.resolve(this.platformInformation);
|
||||
@@ -143,8 +127,7 @@ export class Telemetry {
|
||||
}
|
||||
|
||||
// Augment the properties structure with additional common properties before sending
|
||||
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
|
||||
properties['userId'] = this.userId;
|
||||
Promise.all([this.getPlatformInformation()]).then(() => {
|
||||
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
|
||||
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.7":
|
||||
version "0.1.7"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/d50285b03d0d5073c086362c5c96afb279320607"
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/c7a691c7fc5ad54a147090784a7f11efda301f4b"
|
||||
dependencies:
|
||||
vscode-languageclient "3.5.0"
|
||||
|
||||
@@ -296,9 +296,9 @@ semver@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/2aa9b336b6442e17e24693ddc907030575539798"
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
|
||||
dependencies:
|
||||
decompress "^4.2.0"
|
||||
eventemitter2 "^5.0.1"
|
||||
|
||||
@@ -10,6 +10,13 @@ Common SQL Profiler use-cases taken from https://docs.microsoft.com/en-us/sql/to
|
||||
- Monitoring the performance of SQL Server to tune workloads.
|
||||
- Correlating performance counters to diagnose problems.
|
||||
|
||||
## SQL Server Profiler 0.1.1 Release
|
||||
The SQL Server Profiler for SQL Operations Studio *Preview* extension is now available. This is the initial preview release for a new lightweight XEvent-based profiler. The SQL Server Profiler extension tries to make it simple to quickly trace server activity for troubleshooting and monitoring.
|
||||
|
||||
We'll continue to enhance this extension over the next couple releases. Take a look at the below screenshot to see what's currently available.
|
||||
|
||||
<img width="850" src="https://user-images.githubusercontent.com/599935/41578613-fa10e8bc-7347-11e8-8b97-9fb7d186c9f6.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.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "profiler",
|
||||
"displayName": "SQL Server Profiler",
|
||||
"description": "SQL Server Profiler for SQL Operations Studio",
|
||||
"version": "0.30.0",
|
||||
"version": "0.1.2",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
@@ -32,6 +32,16 @@
|
||||
"command": "profiler.newProfiler",
|
||||
"title": "New Profiler",
|
||||
"category": "Profiler"
|
||||
},
|
||||
{
|
||||
"command": "profiler.start",
|
||||
"title": "Start",
|
||||
"category": "Profiler"
|
||||
},
|
||||
{
|
||||
"command": "profiler.stop",
|
||||
"title": "Stop",
|
||||
"category": "Profiler"
|
||||
}
|
||||
],
|
||||
"outputChannels": [
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string"] },
|
||||
{ "open": "N'", "close": "'", "notIn": ["string", "comment"] },
|
||||
{ "open": "'", "close": "'", "notIn": ["string", "comment"] }
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sqlops",
|
||||
"version": "0.30.5",
|
||||
"version": "0.32.2",
|
||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
@@ -34,7 +34,7 @@
|
||||
"@angular/router": "~4.1.3",
|
||||
"@angular/upgrade": "~4.1.3",
|
||||
"angular2-grid": "2.0.6",
|
||||
"angular2-slickgrid": "git://github.com/Microsoft/angular2-slickgrid.git#1.3.11",
|
||||
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.3.12",
|
||||
"applicationinsights": "0.18.0",
|
||||
"chart.js": "^2.6.0",
|
||||
"fast-plist": "0.1.2",
|
||||
@@ -43,8 +43,8 @@
|
||||
"getmac": "1.0.7",
|
||||
"graceful-fs": "4.1.11",
|
||||
"html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.4",
|
||||
"http-proxy-agent": "0.2.7",
|
||||
"https-proxy-agent": "0.3.6",
|
||||
"http-proxy-agent": "^2.1.0",
|
||||
"https-proxy-agent": "^2.2.1",
|
||||
"iconv-lite": "0.4.19",
|
||||
"jquery": "3.1.0",
|
||||
"jschardet": "1.6.0",
|
||||
@@ -60,7 +60,7 @@
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"rxjs": "5.4.0",
|
||||
"semver": "4.3.6",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.16",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.25",
|
||||
"spdlog": "0.6.0",
|
||||
"sudo-prompt": "^8.0.0",
|
||||
"svg.js": "^2.2.5",
|
||||
|
||||
@@ -27,17 +27,20 @@
|
||||
"reportIssueUrl": "https://github.com/Microsoft/sqlopsstudio/issues/new?labels=customer%20reported%20issue",
|
||||
"requestFeatureUrl": "https://github.com/Microsoft/sqlopsstudio/issues/new?labels=feature-request"
|
||||
},
|
||||
"releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=862039",
|
||||
"gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039",
|
||||
"releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578",
|
||||
"documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277",
|
||||
"vscodeVersion": "1.23.1",
|
||||
"commit": "9ca6200018fc206d67a47229f991901a8a453781",
|
||||
"date": "2017-12-15T12:00:00.000Z",
|
||||
"recommendedExtensions": [
|
||||
"Microsoft.agent",
|
||||
"Microsoft.whoisactive",
|
||||
"Microsoft.profiler",
|
||||
"Microsoft.server-report",
|
||||
"Microsoft.whoisactive",
|
||||
"Redgate.sql-search"
|
||||
],
|
||||
"extensionsGallery": {
|
||||
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ declare @dbsize table
|
||||
Free_Space_MB decimal(20,2) default (0))
|
||||
insert into @dbsize
|
||||
(Dbname,file_Size_MB,Space_Used_MB,Free_Space_MB)
|
||||
exec sp_msforeachdb
|
||||
exec sp_MSforeachdb
|
||||
'use [?];
|
||||
select DB_NAME() AS DbName,
|
||||
sum(size)/128.0 AS File_Size_MB,
|
||||
@@ -24,7 +24,7 @@ declare @logsize table
|
||||
log_Free_Space_MB decimal(20,2)default (0))
|
||||
insert into @logsize
|
||||
(Dbname,Log_File_Size_MB,log_Space_Used_MB,log_Free_Space_MB)
|
||||
exec sp_msforeachdb
|
||||
exec sp_MSforeachdb
|
||||
'use [?];
|
||||
select DB_NAME() AS DbName,
|
||||
sum(size)/128.0 AS Log_File_Size_MB,
|
||||
@@ -38,7 +38,7 @@ declare @dbfreesize table
|
||||
Freespace varchar(50)default (0.00))
|
||||
insert into @dbfreesize
|
||||
(name,database_size,Freespace)
|
||||
exec sp_msforeachdb
|
||||
exec sp_MSforeachdb
|
||||
'use [?];SELECT database_name = db_name()
|
||||
,database_size = ltrim(str((convert(DECIMAL(15, 2), dbsize) + convert(DECIMAL(15, 2), logsize)) * 8192 / 1048576, 15, 2) + ''MB'')
|
||||
,''unallocated space'' = ltrim(str((
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"commands": [
|
||||
{
|
||||
"command": "sqlservices.openDialog",
|
||||
"title": "openDialog"
|
||||
"title": "sqlservices.openDialog"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openEditor",
|
||||
@@ -32,6 +32,10 @@
|
||||
{
|
||||
"command": "sqlservices.openEditorWithWebView2",
|
||||
"title": "sqlservices.openEditorWithWebView2"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openWizard",
|
||||
"title": "sqlservices.openWizard"
|
||||
}
|
||||
],
|
||||
"dashboard.tabs": [
|
||||
|
||||
@@ -60,9 +60,212 @@ export default class MainController implements vscode.Disposable {
|
||||
this.openEditorWithWebview2();
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('sqlservices.openWizard', () => {
|
||||
this.openWizard();
|
||||
});
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private async getTabContent(view: sqlops.ModelView, customButton1: sqlops.window.modelviewdialog.Button, customButton2: sqlops.window.modelviewdialog.Button, componentWidth: number | string): Promise<void> {
|
||||
let inputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
multiline: true,
|
||||
height: 100
|
||||
}).component();
|
||||
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
|
||||
inputBoxWrapper.loading = false;
|
||||
customButton1.onClick(() => {
|
||||
inputBoxWrapper.loading = true;
|
||||
setTimeout(() => inputBoxWrapper.loading = false, 5000);
|
||||
});
|
||||
let inputBox2 = view.modelBuilder.inputBox().component();
|
||||
let backupFilesInputBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
let checkbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: 'Copy-only backup'
|
||||
})
|
||||
.component();
|
||||
checkbox.onChanged(e => {
|
||||
console.info("inputBox.enabled " + inputBox.enabled);
|
||||
inputBox.enabled = !inputBox.enabled;
|
||||
});
|
||||
let button = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '+'
|
||||
}).component();
|
||||
let button3 = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '-'
|
||||
|
||||
}).component();
|
||||
let button2 = view.modelBuilder.button()
|
||||
.component();
|
||||
button.onDidClick(e => {
|
||||
backupFilesInputBox.value = 'Button clicked';
|
||||
});
|
||||
let dropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: 'Full',
|
||||
values: ['Full', 'Differential', 'Transaction Log']
|
||||
})
|
||||
.component();
|
||||
let f = 0;
|
||||
inputBox.onTextChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox.value);
|
||||
f = f + 1;
|
||||
inputBox2.value = f.toString();
|
||||
});
|
||||
dropdown.onValueChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox2.value);
|
||||
inputBox.value = dropdown.value.toString();
|
||||
});
|
||||
let radioButton = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'option1',
|
||||
name: 'radioButtonOptions',
|
||||
label: 'Option 1',
|
||||
checked: true
|
||||
//width: 300
|
||||
}).component();
|
||||
let radioButton2 = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'option2',
|
||||
name: 'radioButtonOptions',
|
||||
label: 'Option 2'
|
||||
}).component();
|
||||
let inputBox3 = view.modelBuilder.inputBox().component();
|
||||
let inputBox4 = view.modelBuilder.inputBox().component();
|
||||
let form2Model = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBox3,
|
||||
title: 'inputBox3'
|
||||
}, {
|
||||
component: inputBox4,
|
||||
title: 'inputBox4'
|
||||
}], {
|
||||
horizontal: true
|
||||
}).component();
|
||||
let groupModel1 = view.modelBuilder.groupContainer()
|
||||
.withLayout({
|
||||
}).withItems([
|
||||
form2Model
|
||||
]).component();
|
||||
radioButton.onDidClick(() => {
|
||||
inputBox.value = radioButton.value;
|
||||
groupModel1.enabled = true;
|
||||
});
|
||||
radioButton2.onDidClick(() => {
|
||||
inputBox.value = radioButton.value;
|
||||
groupModel1.enabled = false;
|
||||
});
|
||||
let table = view.modelBuilder.table().withProperties({
|
||||
data: [
|
||||
['1', '2', '2'],
|
||||
['4', '5', '6'],
|
||||
['7', '8', '9']
|
||||
], columns: ['c1', 'c2', 'c3'],
|
||||
height: 250,
|
||||
selectedRows: [0]
|
||||
}).component();
|
||||
table.onRowSelected(e => {
|
||||
// TODO:
|
||||
});
|
||||
let listBox = view.modelBuilder.listBox().withProperties({
|
||||
values: ['1', '2', '3'],
|
||||
selectedRow: 2
|
||||
}).component();
|
||||
|
||||
let declarativeTable = view.modelBuilder.declarativeTable()
|
||||
.withProperties({
|
||||
columns: [{
|
||||
displayName: 'Column 1',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '20px',
|
||||
isReadOnly: true
|
||||
}, {
|
||||
displayName: 'Column 2',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '100px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: 'Column 3',
|
||||
valueType: sqlops.DeclarativeDataType.boolean,
|
||||
width: '20px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: 'Column 4',
|
||||
valueType: sqlops.DeclarativeDataType.category,
|
||||
isReadOnly: false,
|
||||
width: '120px',
|
||||
categoryValues: [
|
||||
{ name: 'options1', displayName: 'option 1' },
|
||||
{ name: 'options2', displayName: 'option 2' }
|
||||
]
|
||||
}
|
||||
],
|
||||
data: [
|
||||
['Data00', 'Data01', false, 'options2'],
|
||||
['Data10', 'Data11', true, 'options1']
|
||||
]
|
||||
}).component();
|
||||
|
||||
declarativeTable.onDataChanged(e => {
|
||||
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
|
||||
inputBox3.value = declarativeTable.data[e.row][e.column];
|
||||
});
|
||||
|
||||
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'left',
|
||||
height: 150
|
||||
}).withItems([
|
||||
radioButton, groupModel1, radioButton2]
|
||||
, { flex: '1 1 50%' }).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBoxWrapper,
|
||||
title: 'Backup name'
|
||||
}, {
|
||||
component: inputBox2,
|
||||
title: 'Recovery model'
|
||||
}, {
|
||||
component: dropdown,
|
||||
title: 'Backup type'
|
||||
}, {
|
||||
component: checkbox,
|
||||
title: ''
|
||||
}, {
|
||||
component: backupFilesInputBox,
|
||||
title: 'Backup files',
|
||||
actions: [button, button3]
|
||||
}, {
|
||||
component: flexRadioButtonsModel,
|
||||
title: 'Options'
|
||||
}, {
|
||||
component: declarativeTable,
|
||||
title: 'Declarative Table'
|
||||
}, {
|
||||
component: table,
|
||||
title: 'Table'
|
||||
}, {
|
||||
component: listBox,
|
||||
title: 'List Box'
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: componentWidth
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
customButton2.onClick(() => {
|
||||
formWrapper.loading = true;
|
||||
setTimeout(() => formWrapper.loading = false, 5000);
|
||||
});
|
||||
await view.initializeModel(formWrapper);
|
||||
}
|
||||
|
||||
private openDialog(): void {
|
||||
let dialog = sqlops.window.modelviewdialog.createDialog('Test dialog');
|
||||
let tab1 = sqlops.window.modelviewdialog.createTab('Test tab 1');
|
||||
@@ -80,207 +283,40 @@ export default class MainController implements vscode.Disposable {
|
||||
customButton2.onClick(() => console.log('button 2 clicked!'));
|
||||
dialog.customButtons = [customButton1, customButton2];
|
||||
tab1.registerContent(async (view) => {
|
||||
let inputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
multiline: true,
|
||||
height: 100
|
||||
}).component();
|
||||
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
|
||||
inputBoxWrapper.loading = false;
|
||||
customButton1.onClick(() => {
|
||||
inputBoxWrapper.loading = true;
|
||||
setTimeout(() => inputBoxWrapper.loading = false, 5000);
|
||||
});
|
||||
let inputBox2 = view.modelBuilder.inputBox().component();
|
||||
let backupFilesInputBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
let checkbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: 'Copy-only backup'
|
||||
})
|
||||
.component();
|
||||
checkbox.onChanged(e => {
|
||||
console.info("inputBox.enabled " + inputBox.enabled);
|
||||
inputBox.enabled = !inputBox.enabled;
|
||||
});
|
||||
let button = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '+'
|
||||
}).component();
|
||||
let button3 = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '-'
|
||||
|
||||
}).component();
|
||||
let button2 = view.modelBuilder.button()
|
||||
.component();
|
||||
button.onDidClick(e => {
|
||||
backupFilesInputBox.value = 'Button clicked';
|
||||
});
|
||||
let dropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: 'Full',
|
||||
values: ['Full', 'Differential', 'Transaction Log']
|
||||
})
|
||||
.component();
|
||||
let f = 0;
|
||||
inputBox.onTextChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox.value);
|
||||
f = f + 1;
|
||||
inputBox2.value = f.toString();
|
||||
});
|
||||
dropdown.onValueChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox2.value);
|
||||
inputBox.value = dropdown.value;
|
||||
});
|
||||
let radioButton = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'option1',
|
||||
name: 'radioButtonOptions',
|
||||
label: 'Option 1',
|
||||
checked: true
|
||||
//width: 300
|
||||
}).component();
|
||||
let radioButton2 = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'option2',
|
||||
name: 'radioButtonOptions',
|
||||
label: 'Option 2'
|
||||
}).component();
|
||||
let inputBox3 = view.modelBuilder.inputBox().component();
|
||||
let inputBox4 = view.modelBuilder.inputBox().component();
|
||||
let form2Model = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBox3,
|
||||
title: 'inputBox3'
|
||||
}, {
|
||||
component: inputBox4,
|
||||
title: 'inputBox4'
|
||||
}], {
|
||||
horizontal: true
|
||||
}).component();
|
||||
let groupModel1 = view.modelBuilder.groupContainer()
|
||||
.withLayout({
|
||||
}).withItems([
|
||||
form2Model
|
||||
]).component();
|
||||
radioButton.onDidClick(() => {
|
||||
inputBox.value = radioButton.value;
|
||||
groupModel1.enabled = true;
|
||||
});
|
||||
radioButton2.onDidClick(() => {
|
||||
inputBox.value = radioButton.value;
|
||||
groupModel1.enabled = false;
|
||||
});
|
||||
let table = view.modelBuilder.table().withProperties({
|
||||
data: [
|
||||
['1', '2', '2'],
|
||||
['4', '5', '6'],
|
||||
['7', '8', '9']
|
||||
], columns: ['c1', 'c2', 'c3'],
|
||||
height: 250,
|
||||
selectedRows: [0]
|
||||
}).component();
|
||||
table.onRowSelected(e => {
|
||||
// TODO:
|
||||
});
|
||||
let listBox = view.modelBuilder.listBox().withProperties({
|
||||
values: ['1', '2', '3'],
|
||||
selectedRow: 2
|
||||
}).component();
|
||||
|
||||
let declarativeTable = view.modelBuilder.declarativeTable()
|
||||
.withProperties({
|
||||
columns: [{
|
||||
displayName: 'Column 1',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '20px',
|
||||
isReadOnly: true
|
||||
}, {
|
||||
displayName: 'Column 2',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '100px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: 'Column 3',
|
||||
valueType: sqlops.DeclarativeDataType.boolean,
|
||||
width: '20px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: 'Column 4',
|
||||
valueType: sqlops.DeclarativeDataType.category,
|
||||
isReadOnly: false,
|
||||
width: '120px',
|
||||
categoryValues: [
|
||||
{ name: 'options1', displayName: 'option 1' },
|
||||
{ name: 'options2', displayName: 'option 2' }
|
||||
]
|
||||
}
|
||||
],
|
||||
data: [
|
||||
['Data00', 'Data01', false, 'options2'],
|
||||
['Data10', 'Data11', true, 'options1']
|
||||
]
|
||||
}).component();
|
||||
|
||||
declarativeTable.onDataChanged(e => {
|
||||
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
|
||||
inputBox3.value = declarativeTable.data[e.row][e.column];
|
||||
});
|
||||
|
||||
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'left',
|
||||
height: 150
|
||||
}).withItems([
|
||||
radioButton, groupModel1, radioButton2]
|
||||
, { flex: '1 1 50%' }).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBoxWrapper,
|
||||
title: 'Backup name'
|
||||
}, {
|
||||
component: inputBox2,
|
||||
title: 'Recovery model'
|
||||
}, {
|
||||
component: dropdown,
|
||||
title: 'Backup type'
|
||||
}, {
|
||||
component: checkbox,
|
||||
title: ''
|
||||
}, {
|
||||
component: backupFilesInputBox,
|
||||
title: 'Backup files',
|
||||
actions: [button, button3]
|
||||
}, {
|
||||
component: flexRadioButtonsModel,
|
||||
title: 'Options'
|
||||
}, {
|
||||
component: declarativeTable,
|
||||
title: 'Declarative Table'
|
||||
}, {
|
||||
component: table,
|
||||
title: 'Table'
|
||||
}, {
|
||||
component: listBox,
|
||||
title: 'List Box'
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: 400
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
customButton2.onClick(() => {
|
||||
formWrapper.loading = true;
|
||||
setTimeout(() => formWrapper.loading = false, 5000);
|
||||
});
|
||||
await view.initializeModel(formWrapper);
|
||||
await this.getTabContent(view, customButton1, customButton2, 400);
|
||||
});
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(dialog);
|
||||
}
|
||||
|
||||
private openWizard(): void {
|
||||
let wizard = sqlops.window.modelviewdialog.createWizard('Test wizard');
|
||||
let page1 = sqlops.window.modelviewdialog.createWizardPage('First wizard page');
|
||||
let page2 = sqlops.window.modelviewdialog.createWizardPage('Second wizard page');
|
||||
page2.content = 'sqlservices';
|
||||
let customButton1 = sqlops.window.modelviewdialog.createButton('Load name');
|
||||
customButton1.onClick(() => console.log('button 1 clicked!'));
|
||||
let customButton2 = sqlops.window.modelviewdialog.createButton('Load all');
|
||||
customButton2.onClick(() => console.log('button 2 clicked!'));
|
||||
wizard.customButtons = [customButton1, customButton2];
|
||||
page1.registerContent(async (view) => {
|
||||
await this.getTabContent(view, customButton1, customButton2, 800);
|
||||
});
|
||||
/*
|
||||
wizard.registerOperation({
|
||||
displayName: 'test task',
|
||||
description: 'task description',
|
||||
isCancelable: true
|
||||
}, op => {
|
||||
op.updateStatus(sqlops.TaskStatus.InProgress);
|
||||
op.updateStatus(sqlops.TaskStatus.InProgress, 'Task is running');
|
||||
setTimeout(() => {
|
||||
op.updateStatus(sqlops.TaskStatus.Succeeded);
|
||||
}, 5000);
|
||||
});*/
|
||||
wizard.pages = [page1, page2];
|
||||
wizard.open();
|
||||
}
|
||||
|
||||
private openEditor(): void {
|
||||
let editor = sqlops.workspace.createModelViewEditor('Test Model View');
|
||||
editor.registerContent(async view => {
|
||||
@@ -319,15 +355,32 @@ export default class MainController implements vscode.Disposable {
|
||||
webview2.message = count;
|
||||
});
|
||||
|
||||
let flexModel = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'flex-start',
|
||||
height: 500
|
||||
}).withItems([
|
||||
webview1, webview2
|
||||
], { flex: '1 1 50%' })
|
||||
let editor1 = view.modelBuilder.editor()
|
||||
.withProperties({
|
||||
content: 'select * from sys.tables'
|
||||
})
|
||||
.component();
|
||||
|
||||
let editor2 = view.modelBuilder.editor()
|
||||
.withProperties({
|
||||
content: 'print("Hello World !")',
|
||||
languageMode: 'python'
|
||||
})
|
||||
.component();
|
||||
|
||||
let flexModel = view.modelBuilder.flexContainer().component();
|
||||
flexModel.addItem(editor1, { flex: '1' });
|
||||
flexModel.addItem(editor2, { flex: '1' });
|
||||
flexModel.setLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'stretch',
|
||||
height: '100%'
|
||||
});
|
||||
|
||||
view.onClosed((params) => {
|
||||
vscode.window.showInformationMessage('editor1: language: ' + editor1.languageMode + ' Content1: ' + editor1.content);
|
||||
vscode.window.showInformationMessage('editor2: language: ' + editor2.languageMode + ' Content2: ' + editor2.content);
|
||||
});
|
||||
await view.initializeModel(flexModel);
|
||||
});
|
||||
editor.openEditor();
|
||||
@@ -354,13 +407,14 @@ export default class MainController implements vscode.Disposable {
|
||||
let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
|
||||
let monitorIcon = {
|
||||
light: monitorLightPath,
|
||||
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg') };
|
||||
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg')
|
||||
};
|
||||
|
||||
let monitorButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: 'Monitor',
|
||||
iconPath: monitorIcon
|
||||
}).component();
|
||||
.withProperties({
|
||||
label: 'Monitor',
|
||||
iconPath: monitorIcon
|
||||
}).component();
|
||||
let toolbarModel = view.modelBuilder.toolbarContainer()
|
||||
.withToolbarItems([{
|
||||
component: inputBox,
|
||||
|
||||
@@ -11,7 +11,7 @@ export class ToggleDropdownAction extends Action {
|
||||
private static readonly ID = 'dropdownAction.toggle';
|
||||
private static readonly ICON = 'dropdown-arrow';
|
||||
|
||||
constructor(label, private _fn: () => any) {
|
||||
constructor(private _fn: () => any, label: string) {
|
||||
super(ToggleDropdownAction.ID, label, ToggleDropdownAction.ICON);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ export interface IDropdownOptions extends IDropdownStyles {
|
||||
* Value to use as aria-label for the input box
|
||||
*/
|
||||
ariaLabel?: string;
|
||||
/**
|
||||
* Label for the dropdown action
|
||||
*/
|
||||
actionLabel: string;
|
||||
}
|
||||
|
||||
export interface IDropdownStyles {
|
||||
@@ -66,7 +70,8 @@ const defaults: IDropdownOptions = {
|
||||
strictSelection: true,
|
||||
maxHeight: 300,
|
||||
errorMessage: errorMessage,
|
||||
contextBorder: Color.fromHex('#696969')
|
||||
contextBorder: Color.fromHex('#696969'),
|
||||
actionLabel: nls.localize('dropdownAction.toggle', "Toggle dropdown")
|
||||
};
|
||||
|
||||
interface ListResource {
|
||||
@@ -115,11 +120,11 @@ export class Dropdown extends Disposable {
|
||||
this.$input = $('.dropdown-input').style('width', '100%').appendTo(this.$el);
|
||||
this.$treeContainer = $('.dropdown-tree');
|
||||
|
||||
this._toggleAction = new ToggleDropdownAction(nls.localize('dropdown.toggle', '{0} Toggle Dropdown', this._options.ariaLabel), () => {
|
||||
this._toggleAction = new ToggleDropdownAction(() => {
|
||||
this._showList();
|
||||
this._tree.domFocus();
|
||||
this._tree.focusFirst();
|
||||
});
|
||||
}, opt.actionLabel);
|
||||
|
||||
this._input = new InputBox(this.$input.getHTMLElement(), contextViewService, {
|
||||
validationOptions: {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 {
|
||||
Component, Inject, forwardRef, ElementRef, OnInit, Input,
|
||||
Output, OnChanges, SimpleChanges, EventEmitter
|
||||
} from '@angular/core';
|
||||
|
||||
import { Dropdown, IDropdownOptions } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
@Component({
|
||||
selector: 'editable-select-box',
|
||||
template: ''
|
||||
})
|
||||
export class EditableDropDown extends AngularDisposable implements OnInit, OnChanges {
|
||||
private _selectbox: Dropdown;
|
||||
|
||||
@Input() options: string[];
|
||||
@Input() selectedOption: string;
|
||||
@Input() onlyEmitOnChange = false;
|
||||
|
||||
@Output() onDidSelect = new EventEmitter<string>();
|
||||
|
||||
private _previousVal: string;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(IThemeService) private themeService: IThemeService,
|
||||
@Inject(IContextViewService) private contextViewService: IContextViewService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
let dropdownOptions: IDropdownOptions = {
|
||||
values: [],
|
||||
strictSelection: false,
|
||||
placeholder: '',
|
||||
maxHeight: 125,
|
||||
ariaLabel: '',
|
||||
actionLabel: ''
|
||||
};
|
||||
this._selectbox = new Dropdown(this._el.nativeElement, this.contextViewService, this.themeService, dropdownOptions);
|
||||
this._selectbox.values = this.options;
|
||||
this._selectbox.value = this.selectedOption;
|
||||
|
||||
this._selectbox.onValueChange(e => {
|
||||
if (this.onlyEmitOnChange) {
|
||||
if (this._previousVal !== e) {
|
||||
this.onDidSelect.emit(e);
|
||||
this._previousVal = e;
|
||||
}
|
||||
} else {
|
||||
this.onDidSelect.emit(e);
|
||||
}
|
||||
});
|
||||
this._register(attachEditableDropdownStyler(this._selectbox, this.themeService));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this._selectbox.value;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles, IMessage } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
@@ -33,6 +33,7 @@ export class InputBox extends vsInputBox {
|
||||
public onLoseFocus: Event<OnLoseFocusParams> = this._onLoseFocus.event;
|
||||
|
||||
private _isTextAreaInput: boolean;
|
||||
private _hideErrors = false;
|
||||
|
||||
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options?: IInputOptions) {
|
||||
super(container, contextViewProvider, options);
|
||||
@@ -62,13 +63,13 @@ export class InputBox extends vsInputBox {
|
||||
this.enabledInputBorder = this.inputBorder;
|
||||
this.disabledInputBackground = styles.disabledInputBackground;
|
||||
this.disabledInputForeground = styles.disabledInputForeground;
|
||||
this.updateInputEnabledDisabledColors();
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public enable(): void {
|
||||
super.enable();
|
||||
this.inputBackground = this.enabledInputBackground;
|
||||
this.inputForeground = this.enabledInputForeground;
|
||||
this.inputBorder = this.enabledInputBorder;
|
||||
this.updateInputEnabledDisabledColors();
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
@@ -88,9 +89,7 @@ export class InputBox extends vsInputBox {
|
||||
|
||||
public disable(): void {
|
||||
super.disable();
|
||||
this.inputBackground = this.disabledInputBackground;
|
||||
this.inputForeground = this.disabledInputForeground;
|
||||
this.inputBorder = this.disabledInputBorder;
|
||||
this.updateInputEnabledDisabledColors();
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
@@ -103,4 +102,28 @@ export class InputBox extends vsInputBox {
|
||||
public isEnabled(): boolean {
|
||||
return !this.inputElement.hasAttribute('disabled');
|
||||
}
|
||||
|
||||
public get hideErrors(): boolean {
|
||||
return this._hideErrors;
|
||||
}
|
||||
|
||||
public set hideErrors(hideErrors: boolean) {
|
||||
this._hideErrors = hideErrors;
|
||||
if (hideErrors) {
|
||||
this.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public showMessage(message: IMessage, force?: boolean): void {
|
||||
if (!this.hideErrors) {
|
||||
super.showMessage(message, force);
|
||||
}
|
||||
}
|
||||
|
||||
private updateInputEnabledDisabledColors(): void {
|
||||
let enabled = this.isEnabled();
|
||||
this.inputBackground = enabled ? this.enabledInputBackground : this.disabledInputBackground;
|
||||
this.inputForeground = enabled ? this.enabledInputForeground : this.disabledInputForeground;
|
||||
this.inputBorder = enabled ? this.enabledInputBorder : this.disabledInputBorder;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export function appendRow(container: Builder, label: string, labelClass: string,
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
|
||||
labelCellContainer.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
labelContainer.text(label);
|
||||
});
|
||||
});
|
||||
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
|
||||
@@ -33,7 +33,7 @@ export function appendRowLink(container: Builder, label: string, labelClass: str
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
|
||||
labelCellContainer.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
labelContainer.text(label);
|
||||
});
|
||||
});
|
||||
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
|
||||
|
||||
@@ -172,4 +172,22 @@
|
||||
margin-right: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.modal .modal-footer .dialogErrorMessage {
|
||||
align-items: center;
|
||||
max-height: 30px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.modal .dialogErrorMessage .icon {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: auto;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.modal .modal-footer .dialogErrorMessage .errorMessage {
|
||||
max-height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
@@ -21,9 +21,13 @@ import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
import { localize } from 'vs/nls';
|
||||
import { MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export const MODAL_SHOWING_KEY = 'modalShowing';
|
||||
export const MODAL_SHOWING_CONTEXT = new RawContextKey<Array<string>>(MODAL_SHOWING_KEY, []);
|
||||
const INFO_ALT_TEXT = localize('infoAltText', 'Info');
|
||||
const WARNING_ALT_TEXT = localize('warningAltText', 'Warning');
|
||||
const ERROR_ALT_TEXT = localize('errorAltText', 'Error');
|
||||
|
||||
export interface IModalDialogStyles {
|
||||
dialogForeground?: Color;
|
||||
@@ -145,7 +149,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
/**
|
||||
* Build and render the modal, will call {@link Modal#renderBody}
|
||||
*/
|
||||
public render() {
|
||||
public render(errorMessagesInFooter: boolean = false) {
|
||||
let modalBodyClass = (this._modalOptions.isAngular === false ? 'modal-body' : 'modal-body-and-footer');
|
||||
let parts: Array<HTMLElement> = [];
|
||||
// This modal header section refers to the header of of the dialog
|
||||
@@ -166,7 +170,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
}
|
||||
modalHeader.div({ class: 'modal-title' }, (modalTitle) => {
|
||||
this._modalTitle = modalTitle;
|
||||
modalTitle.innerHtml(this._title);
|
||||
modalTitle.text(this._title);
|
||||
});
|
||||
});
|
||||
parts.push(this._modalHeaderSection.getHTMLElement());
|
||||
@@ -182,17 +186,6 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
|
||||
this.renderBody(body.getHTMLElement());
|
||||
|
||||
if (this._modalOptions.isAngular === false && this._modalOptions.hasErrors) {
|
||||
body.div({ class: 'dialogErrorMessage', id: 'dialogErrorMessage' }, (errorMessageContainer) => {
|
||||
errorMessageContainer.div({ class: 'icon error' }, (iconContainer) => {
|
||||
this._errorIconElement = iconContainer.getHTMLElement();
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
});
|
||||
errorMessageContainer.div({ class: 'errorMessage' }, (messageContainer) => {
|
||||
this._errorMessage = messageContainer;
|
||||
});
|
||||
});
|
||||
}
|
||||
// This modal footer section refers to the footer of of the dialog
|
||||
if (this._modalOptions.isAngular === false) {
|
||||
this._modalFooterSection = $().div({ class: 'modal-footer' }, (modelFooter) => {
|
||||
@@ -221,6 +214,19 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
builderClass += ' wide';
|
||||
}
|
||||
|
||||
if (this._modalOptions.isAngular === false && this._modalOptions.hasErrors) {
|
||||
let builder = errorMessagesInFooter ? this._leftFooter : body;
|
||||
builder.div({ class: 'dialogErrorMessage', id: 'dialogErrorMessage' }, (errorMessageContainer) => {
|
||||
errorMessageContainer.div({ class: 'icon error' }, (iconContainer) => {
|
||||
this._errorIconElement = iconContainer.getHTMLElement();
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
});
|
||||
errorMessageContainer.div({ class: 'errorMessage' }, (messageContainer) => {
|
||||
this._errorMessage = messageContainer;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// The builder builds the dialog. It append header, body and footer sections.
|
||||
this._builder = $().div({ class: builderClass, 'role': 'dialog' }, (dialogContainer) => {
|
||||
this._modalDialog = dialogContainer.div({ class: 'modal-dialog ', role: 'document' }, (modalDialog) => {
|
||||
@@ -355,14 +361,33 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
* Show an error in the error message element
|
||||
* @param err Text to show in the error message
|
||||
*/
|
||||
protected setError(err: string) {
|
||||
protected setError(err: string, level: MessageLevel = MessageLevel.Error) {
|
||||
if (this._modalOptions.hasErrors) {
|
||||
if (err === '') {
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
} else {
|
||||
const levelClasses = ['info', 'warning', 'error'];
|
||||
let selectedLevel = levelClasses[2];
|
||||
let altText = ERROR_ALT_TEXT;
|
||||
if (level === MessageLevel.Information) {
|
||||
selectedLevel = levelClasses[0];
|
||||
altText = INFO_ALT_TEXT;
|
||||
} else if (level === MessageLevel.Warning) {
|
||||
selectedLevel = levelClasses[1];
|
||||
altText = WARNING_ALT_TEXT;
|
||||
}
|
||||
levelClasses.forEach(level => {
|
||||
if (selectedLevel === level) {
|
||||
this._errorIconElement.classList.add(level);
|
||||
} else {
|
||||
this._errorIconElement.classList.remove(level);
|
||||
}
|
||||
});
|
||||
|
||||
this._errorIconElement.title = altText;
|
||||
this._errorIconElement.style.visibility = 'visible';
|
||||
}
|
||||
this._errorMessage.innerHtml(err);
|
||||
this._errorMessage.text(err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +433,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
*/
|
||||
protected set title(title: string) {
|
||||
if (this._title !== undefined) {
|
||||
this._modalTitle.innerHtml(title);
|
||||
this._modalTitle.text(title);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,8 +150,8 @@ export class OptionsDialog extends Modal {
|
||||
|
||||
private onOptionLinkClicked(optionName: string): void {
|
||||
var option = this._optionElements[optionName].option;
|
||||
this._optionTitle.innerHtml(option.displayName);
|
||||
this._optionDescription.innerHtml(option.description);
|
||||
this._optionTitle.text(option.displayName);
|
||||
this._optionDescription.text(option.description);
|
||||
}
|
||||
|
||||
private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void {
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
import { Component, Input, ContentChild, OnDestroy, TemplateRef, ChangeDetectorRef, forwardRef, Inject } from '@angular/core';
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export abstract class TabChild {
|
||||
export abstract class TabChild extends Disposable {
|
||||
public abstract layout(): void;
|
||||
}
|
||||
|
||||
@@ -19,7 +20,7 @@ export abstract class TabChild {
|
||||
`
|
||||
})
|
||||
export class TabComponent implements OnDestroy {
|
||||
@ContentChild(TabChild) private _child: TabChild;
|
||||
private _child: TabChild;
|
||||
@ContentChild(TemplateRef) templateRef;
|
||||
@Input() public title: string;
|
||||
@Input() public canClose: boolean;
|
||||
@@ -29,19 +30,30 @@ export class TabComponent implements OnDestroy {
|
||||
@Input() public identifier: string;
|
||||
@Input() private visibilityType: 'if' | 'visibility' = 'if';
|
||||
private rendered = false;
|
||||
private destroyed: boolean = false;
|
||||
|
||||
|
||||
@ContentChild(TabChild) private set child(tab: TabChild) {
|
||||
this._child = tab;
|
||||
if (this.active && this._child) {
|
||||
this._child.layout();
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
|
||||
) { }
|
||||
|
||||
public set active(val: boolean) {
|
||||
this._active = val;
|
||||
if (this.active) {
|
||||
this.rendered = true;
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
if (this.active && this._child) {
|
||||
this._child.layout();
|
||||
if (!this.destroyed) {
|
||||
this._active = val;
|
||||
if (this.active) {
|
||||
this.rendered = true;
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
if (this.active && this._child) {
|
||||
this._child.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +62,7 @@ export class TabComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroyed = true;
|
||||
if (this.actions && this.actions.length > 0) {
|
||||
this.actions.forEach((action) => action.dispose());
|
||||
}
|
||||
@@ -74,6 +87,8 @@ export class TabComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
public layout() {
|
||||
this._child.layout();
|
||||
if (this._child) {
|
||||
this._child.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
|
||||
tabLabelcontainer.classList.add(this.tab.iconClass);
|
||||
} else {
|
||||
tabLabelcontainer.className = 'tabLabel';
|
||||
tabLabelcontainer.innerHTML = this.tab.title;
|
||||
tabLabelcontainer.textContent = this.tab.title;
|
||||
}
|
||||
tabLabelcontainer.title = this.tab.title;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
{
|
||||
border: 1px solid #BFBDBD;
|
||||
font-size: 8pt;
|
||||
height: 400px;
|
||||
height: 250px;
|
||||
margin-top: 6px;
|
||||
overflow: scroll;
|
||||
padding: 4px;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
/**
|
||||
* Implements the various additional navigation keybindings we want out of slickgrid
|
||||
*/
|
||||
export class AdditionalKeyBindings<T> implements Slick.Plugin<T> {
|
||||
private grid: Slick.Grid<T>;
|
||||
private handler = new Slick.EventHandler();
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this.grid = grid;
|
||||
this.handler.subscribe(this.grid.onKeyDown, (e, args) => this.handleKeyDown(e, args));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>): void {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
let handled = true;
|
||||
|
||||
if (event.equals(KeyCode.RightArrow | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(args.row, this.grid.getColumns().length - 1);
|
||||
} else if (event.equals(KeyCode.LeftArrow | KeyMod.CtrlCmd)) {
|
||||
// account for row column
|
||||
if (this.grid.canCellBeActive(args.row, 0)) {
|
||||
this.grid.setActiveCell(args.row, 0);
|
||||
} else {
|
||||
this.grid.setActiveCell(args.row, 1);
|
||||
}
|
||||
} else if (event.equals(KeyCode.UpArrow | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(0, args.cell);
|
||||
} else if (event.equals(KeyCode.DownArrow | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(this.grid.getDataLength() - 1, args.cell);
|
||||
} else if (event.equals(KeyCode.Home | KeyMod.CtrlCmd)) {
|
||||
// account for row column
|
||||
if (this.grid.canCellBeActive(0, 0)) {
|
||||
this.grid.setActiveCell(0, 0);
|
||||
} else {
|
||||
this.grid.setActiveCell(0, 1);
|
||||
}
|
||||
} else if (event.equals(KeyCode.End | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(this.grid.getDataLength() - 1, this.grid.getColumns().length - 1);
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||
private _context: CanvasRenderingContext2D;
|
||||
private _options: IAutoColumnSizeOptions;
|
||||
|
||||
constructor(options: IAutoColumnSizeOptions) {
|
||||
constructor(options: IAutoColumnSizeOptions = defaultOptions) {
|
||||
this._options = mixin(options, defaultOptions, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
|
||||
// heavily modified
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
|
||||
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangeselector');
|
||||
|
||||
export interface ICellRangeSelector<T> extends Slick.Plugin<T> {
|
||||
onCellRangeSelected: Slick.Event<{ range: Slick.Range }>;
|
||||
onBeforeCellRangeSelected: Slick.Event<Slick.Cell>;
|
||||
}
|
||||
|
||||
export interface ICellSelectionModelOptions {
|
||||
cellRangeSelector?: any;
|
||||
selectActiveCell?: boolean;
|
||||
}
|
||||
|
||||
const defaults: ICellSelectionModelOptions = {
|
||||
selectActiveCell: true
|
||||
};
|
||||
|
||||
export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
|
||||
private grid: Slick.Grid<T>;
|
||||
private selector: ICellRangeSelector<T>;
|
||||
private ranges: Array<Slick.Range> = [];
|
||||
|
||||
public onSelectedRangesChanged = new Slick.Event<Array<Slick.Range>>();
|
||||
|
||||
constructor(private options: ICellSelectionModelOptions = defaults) {
|
||||
this.options = mixin(this.options, defaults, false);
|
||||
|
||||
if (this.options.cellRangeSelector) {
|
||||
this.selector = this.options.cellRangeSelector;
|
||||
} else {
|
||||
// this is added by the noderequires above
|
||||
this.selector = new (<any>Slick).CellRangeSelector({ selectionCss: { 'border': '2px dashed grey' } });
|
||||
}
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this.grid = grid;
|
||||
this.grid.onActiveCellChanged.subscribe((e, args) => this.handleActiveCellChange(e, args));
|
||||
this.grid.onKeyDown.subscribe(e => this.handleKeyDown(e));
|
||||
this.grid.onHeaderClick.subscribe((e: MouseEvent, args) => this.handleHeaderClick(e, args));
|
||||
this.grid.registerPlugin(this.selector);
|
||||
this.selector.onCellRangeSelected.subscribe((e, args) => this.handleCellRangeSelected(e, args));
|
||||
this.selector.onBeforeCellRangeSelected.subscribe((e, args) => this.handleBeforeCellRangeSelected(e, args));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.grid.onActiveCellChanged.unsubscribe((e, args) => this.handleActiveCellChange(e, args));
|
||||
this.grid.onKeyDown.unsubscribe(e => this.handleKeyDown(e));
|
||||
this.selector.onCellRangeSelected.unsubscribe((e, args) => this.handleCellRangeSelected(e, args));
|
||||
this.selector.onBeforeCellRangeSelected.unsubscribe((e, args) => this.handleBeforeCellRangeSelected(e, args));
|
||||
this.grid.unregisterPlugin(this.selector);
|
||||
}
|
||||
|
||||
private removeInvalidRanges(ranges: Array<Slick.Range>): Array<Slick.Range> {
|
||||
let result: Array<Slick.Range> = [];
|
||||
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let r = ranges[i];
|
||||
if (this.grid.canCellBeSelected(r.fromRow, r.fromCell) && this.grid.canCellBeSelected(r.toRow, r.toCell)) {
|
||||
result.push(r);
|
||||
} else if (this.grid.canCellBeSelected(r.fromRow, r.fromCell + 1) && this.grid.canCellBeSelected(r.toRow, r.toCell)) {
|
||||
// account for number row
|
||||
result.push(new Slick.Range(r.fromRow, r.fromCell + 1, r.toRow, r.toCell));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public setSelectedRanges(ranges: Array<Slick.Range>): void {
|
||||
// simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged
|
||||
if ((!this.ranges || this.ranges.length === 0) && (!ranges || ranges.length === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ranges = this.removeInvalidRanges(ranges);
|
||||
this.onSelectedRangesChanged.notify(this.ranges);
|
||||
}
|
||||
|
||||
public getSelectedRanges() {
|
||||
return this.ranges;
|
||||
}
|
||||
|
||||
private handleBeforeCellRangeSelected(e, args: Slick.Cell) {
|
||||
if (this.grid.getEditorLock().isActive()) {
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleCellRangeSelected(e, args: { range: Slick.Range }) {
|
||||
this.grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true);
|
||||
this.setSelectedRanges([args.range]);
|
||||
}
|
||||
|
||||
private handleActiveCellChange(e, args) {
|
||||
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) {
|
||||
this.setSelectedRanges([new Slick.Range(args.row, args.cell)]);
|
||||
} else if (!this.options.selectActiveCell) {
|
||||
// clear the previous selection once the cell changes
|
||||
this.setSelectedRanges([]);
|
||||
}
|
||||
}
|
||||
|
||||
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
|
||||
if (!isUndefinedOrNull(args.column)) {
|
||||
let columnIndex = this.grid.getColumnIndex(args.column.id);
|
||||
if (this.grid.canCellBeSelected(0, columnIndex)) {
|
||||
let ranges: Array<Slick.Range>;
|
||||
if (e.shiftKey) {
|
||||
ranges = this.getSelectedRanges();
|
||||
ranges.push(new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex));
|
||||
} else {
|
||||
ranges = [new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex)];
|
||||
}
|
||||
this.grid.setActiveCell(0, columnIndex);
|
||||
this.setSelectedRanges(ranges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(e) {
|
||||
/***
|
||||
* Кey codes
|
||||
* 37 left
|
||||
* 38 up
|
||||
* 39 right
|
||||
* 40 down
|
||||
*/
|
||||
let ranges, last;
|
||||
let active = this.grid.getActiveCell();
|
||||
let metaKey = e.ctrlKey || e.metaKey;
|
||||
|
||||
if (active && e.shiftKey && !metaKey && !e.altKey &&
|
||||
(e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40)) {
|
||||
|
||||
ranges = this.getSelectedRanges();
|
||||
if (!ranges.length) {
|
||||
ranges.push(new Slick.Range(active.row, active.cell));
|
||||
}
|
||||
|
||||
// keyboard can work with last range only
|
||||
last = ranges.pop();
|
||||
|
||||
// can't handle selection out of active cell
|
||||
if (!last.contains(active.row, active.cell)) {
|
||||
last = new Slick.Range(active.row, active.cell);
|
||||
}
|
||||
|
||||
let dRow = last.toRow - last.fromRow,
|
||||
dCell = last.toCell - last.fromCell,
|
||||
// walking direction
|
||||
dirRow = active.row === last.fromRow ? 1 : -1,
|
||||
dirCell = active.cell === last.fromCell ? 1 : -1;
|
||||
|
||||
if (e.which === 37) {
|
||||
dCell -= dirCell;
|
||||
} else if (e.which === 39) {
|
||||
dCell += dirCell;
|
||||
} else if (e.which === 38) {
|
||||
dRow -= dirRow;
|
||||
} else if (e.which === 40) {
|
||||
dRow += dirRow;
|
||||
}
|
||||
|
||||
// define new selection range
|
||||
let new_last = new Slick.Range(active.row, active.cell, active.row + dirRow * dRow, active.cell + dirCell * dCell);
|
||||
if (this.removeInvalidRanges([new_last]).length) {
|
||||
ranges.push(new_last);
|
||||
let viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow;
|
||||
let viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell;
|
||||
this.grid.scrollRowIntoView(viewRow, false);
|
||||
this.grid.scrollCellIntoView(viewRow, viewCell, false);
|
||||
} else {
|
||||
ranges.push(last);
|
||||
}
|
||||
|
||||
this.setSelectedRanges(ranges);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,364 +0,0 @@
|
||||
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
|
||||
// heavily modified
|
||||
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
|
||||
export class DragCellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
|
||||
private readonly keyColResizeIncr = 5;
|
||||
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _ranges: Array<Slick.Range> = [];
|
||||
private _dragging = false;
|
||||
private _handler = new Slick.EventHandler();
|
||||
|
||||
public onSelectedRangesChanged = new Slick.Event<Slick.Range[]>();
|
||||
|
||||
public init(grid: Slick.Grid<T>): void {
|
||||
this._grid = grid;
|
||||
this._handler.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, data));
|
||||
this._handler.subscribe(this._grid.onKeyDown, (e: JQueryInputEventObject) => this.handleKeyDown(e));
|
||||
this._handler.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e));
|
||||
this._handler.subscribe(this._grid.onDrag, (e: MouseEvent) => this.handleDrag(e));
|
||||
this._handler.subscribe(this._grid.onDragInit, (e: MouseEvent) => this.handleDragInit(e));
|
||||
this._handler.subscribe(this._grid.onDragStart, (e: MouseEvent) => this.handleDragStart(e));
|
||||
this._handler.subscribe(this._grid.onDragEnd, (e: MouseEvent) => this.handleDragEnd(e));
|
||||
this._handler.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this._handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
private rangesToRows(ranges: Array<Slick.Range>): Array<number> {
|
||||
let rows = [];
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
|
||||
rows.push(j);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private rowsToRanges(rows: Array<number>): Array<Slick.Range> {
|
||||
let ranges = [];
|
||||
let lastCell = this._grid.getColumns().length - 1;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
public getSelectedRows(): Array<number> {
|
||||
return this.rangesToRows(this._ranges);
|
||||
}
|
||||
|
||||
public setSelectedRows(rows: Array<number>) {
|
||||
this.setSelectedRanges(this.rowsToRanges(rows));
|
||||
}
|
||||
|
||||
public setSelectedRanges(ranges: Array<Slick.Range>) {
|
||||
this._ranges = ranges;
|
||||
this.onSelectedRangesChanged.notify(this._ranges);
|
||||
}
|
||||
|
||||
public getSelectedRanges(): Array<Slick.Range> {
|
||||
return this._ranges;
|
||||
}
|
||||
|
||||
private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) { }
|
||||
|
||||
private isNavigationKey(e: BaseJQueryEventObject) {
|
||||
// Nave keys (home, end, arrows) are all in sequential order so use a
|
||||
switch (e.which) {
|
||||
case $.ui.keyCode.HOME:
|
||||
case $.ui.keyCode.END:
|
||||
case $.ui.keyCode.LEFT:
|
||||
case $.ui.keyCode.UP:
|
||||
case $.ui.keyCode.RIGHT:
|
||||
case $.ui.keyCode.DOWN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private navigateLeft(e: JQueryInputEventObject, activeCell: Slick.Cell) {
|
||||
if (activeCell.cell > 1) {
|
||||
let isHome = e.which === $.ui.keyCode.HOME;
|
||||
let newActiveCellColumn = isHome ? 1 : activeCell.cell - 1;
|
||||
// Unsure why but for range, must record 1 index less than expected
|
||||
let newRangeColumn = newActiveCellColumn - 1;
|
||||
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the rightmost edge of the range and we navigate left,
|
||||
// we want to deselect the rightmost cell
|
||||
if (last.fromCell <= newRangeColumn) { last.toCell -= 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row, last.fromRow);
|
||||
let fromCell = Math.min(newRangeColumn, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row, last.toRow);
|
||||
let toCell = Math.max(newRangeColumn, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
|
||||
}
|
||||
|
||||
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
|
||||
private navigateRight(e: JQueryInputEventObject, activeCell: Slick.Cell) {
|
||||
let columnLength = this._grid.getColumns().length;
|
||||
if (activeCell.cell < columnLength) {
|
||||
let isEnd = e.which === $.ui.keyCode.END;
|
||||
let newActiveCellColumn = isEnd ? columnLength : activeCell.cell + 1;
|
||||
// Unsure why but for range, must record 1 index less than expected
|
||||
let newRangeColumn = newActiveCellColumn - 1;
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the leftmost edge of the range and we navigate right,
|
||||
// we want to deselect the leftmost cell
|
||||
if (newRangeColumn <= last.toCell) { last.fromCell += 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row, last.fromRow);
|
||||
let fromCell = Math.min(newRangeColumn, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row, last.toRow);
|
||||
let toCell = Math.max(newRangeColumn, last.toCell);
|
||||
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(e: JQueryInputEventObject) {
|
||||
let activeCell = this._grid.getActiveCell();
|
||||
|
||||
if (activeCell) {
|
||||
// navigation keys
|
||||
if (this.isNavigationKey(e)) {
|
||||
e.stopImmediatePropagation();
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
let event = new CustomEvent('gridnav', {
|
||||
detail: {
|
||||
which: e.which,
|
||||
ctrlKey: e.ctrlKey,
|
||||
metaKey: e.metaKey,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
return;
|
||||
}
|
||||
// end key
|
||||
if (e.which === $.ui.keyCode.END) {
|
||||
this.navigateRight(e, activeCell);
|
||||
}
|
||||
// home key
|
||||
if (e.which === $.ui.keyCode.HOME) {
|
||||
this.navigateLeft(e, activeCell);
|
||||
}
|
||||
// left arrow
|
||||
if (e.which === $.ui.keyCode.LEFT) {
|
||||
// column resize
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
let allColumns = clone(this._grid.getColumns());
|
||||
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width - this.keyColResizeIncr;
|
||||
this._grid.setColumnWidths(allColumns);
|
||||
} else {
|
||||
this.navigateLeft(e, activeCell);
|
||||
}
|
||||
// up arrow
|
||||
} else if (e.which === $.ui.keyCode.UP && activeCell.row > 0) {
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the bottommost edge of the range and we navigate up,
|
||||
// we want to deselect the bottommost row
|
||||
let newRangeRow = activeCell.row - 1;
|
||||
if (last.fromRow <= newRangeRow) { last.toRow -= 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row - 1, last.fromRow);
|
||||
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(newRangeRow, last.toRow);
|
||||
let toCell = Math.max(activeCell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row - 1, activeCell.cell - 1, activeCell.row - 1, activeCell.cell - 1)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row - 1, activeCell.cell);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
// right arrow
|
||||
} else if (e.which === $.ui.keyCode.RIGHT) {
|
||||
// column resize
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
let allColumns = clone(this._grid.getColumns());
|
||||
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width + this.keyColResizeIncr;
|
||||
this._grid.setColumnWidths(allColumns);
|
||||
} else {
|
||||
this.navigateRight(e, activeCell);
|
||||
}
|
||||
// down arrow
|
||||
} else if (e.which === $.ui.keyCode.DOWN && activeCell.row < this._grid.getDataLength() - 1) {
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the topmost edge of the range and we navigate down,
|
||||
// we want to deselect the topmost row
|
||||
let newRangeRow = activeCell.row + 1;
|
||||
if (newRangeRow <= last.toRow) { last.fromRow += 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row + 1, last.fromRow);
|
||||
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row + 1, last.toRow);
|
||||
let toCell = Math.max(activeCell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row + 1, activeCell.cell - 1, activeCell.row + 1, activeCell.cell - 1)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row + 1, activeCell.cell);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
|
||||
let columnIndex = this._grid.getColumnIndex(args.column.id);
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
this._ranges.push(new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex));
|
||||
this._grid.setActiveCell(0, columnIndex + 1);
|
||||
} else if (e.shiftKey && this._ranges.length) {
|
||||
let last = this._ranges.pop().fromCell;
|
||||
let from = Math.min(columnIndex, last);
|
||||
let to = Math.max(columnIndex, last);
|
||||
this._ranges = [];
|
||||
for (let i = from; i <= to; i++) {
|
||||
if (i !== last) {
|
||||
this._ranges.push(new Slick.Range(0, i, this._grid.getDataLength() - 1, i));
|
||||
}
|
||||
}
|
||||
this._ranges.push(new Slick.Range(0, last, this._grid.getDataLength() - 1, last));
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex)];
|
||||
this._grid.resetActiveCell();
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
e.stopImmediatePropagation();
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleClick(e: MouseEvent) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
|
||||
if (cell.cell !== 0) {
|
||||
this._ranges = [new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1)];
|
||||
this.setSelectedRanges(this._ranges);
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
return true;
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1)];
|
||||
this.setSelectedRanges(this._ranges);
|
||||
this._grid.setActiveCell(cell.row, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (this._grid.getOptions().multiSelect) {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (cell.cell === 0) {
|
||||
this._ranges.push(new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1));
|
||||
this._grid.setActiveCell(cell.row, 1);
|
||||
} else {
|
||||
this._ranges.push(new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1));
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
}
|
||||
} else if (this._ranges.length && e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
if (cell.cell === 0) {
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let toRow = Math.max(cell.row, last.fromRow);
|
||||
this._ranges = [new Slick.Range(fromRow, 0, toRow, this._grid.getColumns().length - 1)];
|
||||
} else {
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let fromCell = Math.min(cell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(cell.row, last.toRow);
|
||||
let toCell = Math.max(cell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setSelectedRanges(this._ranges);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleDragInit(e: MouseEvent) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
private handleDragStart(e: MouseEvent) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
e.stopImmediatePropagation();
|
||||
this._dragging = true;
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
this._ranges.push(new Slick.Range(cell.row, cell.cell));
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
} else if (this._ranges.length && e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let fromCell = Math.min(cell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(cell.row, last.toRow);
|
||||
let toCell = Math.max(cell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(cell.row, cell.cell)];
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
|
||||
private handleDrag(e: MouseEvent) {
|
||||
if (this._dragging) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
let activeCell = this._grid.getActiveCell();
|
||||
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._ranges.pop();
|
||||
|
||||
if (activeCell.cell === 0) {
|
||||
let lastCell = this._grid.getColumns().length - 1;
|
||||
let firstRow = Math.min(cell.row, activeCell.row);
|
||||
let lastRow = Math.max(cell.row, activeCell.row);
|
||||
this._ranges.push(new Slick.Range(firstRow, 0, lastRow, lastCell));
|
||||
} else {
|
||||
let firstRow = Math.min(cell.row, activeCell.row);
|
||||
let lastRow = Math.max(cell.row, activeCell.row);
|
||||
let firstColumn = Math.min(cell.cell - 1, activeCell.cell - 1);
|
||||
let lastColumn = Math.max(cell.cell - 1, activeCell.cell - 1);
|
||||
this._ranges.push(new Slick.Range(firstRow, firstColumn, lastRow, lastColumn));
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private handleDragEnd(e: MouseEvent) {
|
||||
this._dragging = false;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { mixin } from 'vs/base/common/objects';
|
||||
import { SlickGrid } from 'angular2-slickgrid';
|
||||
import { Button } from '../../button/button';
|
||||
import { attachButtonStyler } from 'sql/common/theme/styler';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export class HeaderFilter {
|
||||
@@ -174,7 +175,7 @@ export class HeaderFilter {
|
||||
if (filterItems[i] && filterItems[i].indexOf('Error:') < 0) {
|
||||
filterOptions += '<label><input type="checkbox" value="' + i + '"'
|
||||
+ (filtered ? ' checked="checked"' : '')
|
||||
+ '/>' + filterItems[i] + '</label>';
|
||||
+ '/>' + escape(filterItems[i]) + '</label>';
|
||||
}
|
||||
}
|
||||
let $filter = $('<div class="filter">')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowdetailview.js
|
||||
// heavily modified
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
@@ -354,7 +355,7 @@ export class RowDetailView {
|
||||
html.push("style='height:", dataContext._height, "px;"); //set total height of padding
|
||||
html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
|
||||
html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
|
||||
html.push("<div id='innerDetailView_", dataContext.id, "'>", dataContext._detailContent, "</div></div>");
|
||||
html.push("<div id='innerDetailView_", dataContext.id, "'>", escape(dataContext._detailContent), "</div></div>");
|
||||
//&omit a final closing detail container </div> that would come next
|
||||
|
||||
return html.join('');
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { range } from 'vs/base/common/arrays';
|
||||
|
||||
export interface IRowNumberColumnOptions {
|
||||
numberOfRows: number;
|
||||
cssClass?: string;
|
||||
}
|
||||
|
||||
const sizePerDigit = 15;
|
||||
|
||||
export class RowNumberColumn<T> implements Slick.Plugin<T> {
|
||||
private handler = new Slick.EventHandler();
|
||||
private grid: Slick.Grid<T>;
|
||||
|
||||
|
||||
constructor(private options: IRowNumberColumnOptions) {
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this.grid = grid;
|
||||
this.handler
|
||||
.subscribe(this.grid.onClick, (e, args) => this.handleClick(e, args))
|
||||
.subscribe(this.grid.onHeaderClick, (e, args) => this.handleHeaderClick(e, args));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
private handleClick(e: MouseEvent, args: Slick.OnClickEventArgs<T>): void {
|
||||
if (this.grid.getColumns()[args.cell].id === 'rowNumber') {
|
||||
this.grid.setActiveCell(args.row, 1);
|
||||
this.grid.setSelectedRows([args.row]);
|
||||
}
|
||||
}
|
||||
|
||||
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>): void {
|
||||
if (args.column.id === 'rowNumber') {
|
||||
this.grid.setActiveCell(0, 1);
|
||||
this.grid.setSelectedRows(range(this.grid.getDataLength()));
|
||||
}
|
||||
}
|
||||
|
||||
public getColumnDefinition(): Slick.Column<T> {
|
||||
return {
|
||||
id: 'rowNumber',
|
||||
name: '',
|
||||
field: 'rowNumber',
|
||||
width: this.options.numberOfRows.toString().length * sizePerDigit,
|
||||
resizable: false,
|
||||
cssClass: this.options.cssClass,
|
||||
focusable: false,
|
||||
selectable: false,
|
||||
formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc)
|
||||
};
|
||||
}
|
||||
|
||||
private formatter(row, cell, value, columnDef: Slick.Column<T>, dataContext): string {
|
||||
if (dataContext) {
|
||||
return `<span>${row}</span>`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export interface IFindPosition {
|
||||
row: number;
|
||||
}
|
||||
|
||||
export function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||
function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||
let field = args.sortCol.field;
|
||||
let sign = args.sortAsc ? 1 : -1;
|
||||
return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign);
|
||||
|
||||
@@ -82,7 +82,7 @@ export class Taskbar {
|
||||
public static createTaskbarText(inputText: string): HTMLElement {
|
||||
let element = document.createElement('div');
|
||||
element.className = 'taskbarTextSeparator';
|
||||
element.innerHTML = inputText;
|
||||
element.textContent = inputText;
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
22
src/sql/base/common/strings.ts
Normal file
22
src/sql/base/common/strings.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Converts HTML characters inside the string to use entities instead. Makes the string safe from
|
||||
* being used e.g. in HTMLElement.innerHTML.
|
||||
*/
|
||||
export function escape(html: string): string {
|
||||
return html.replace(/[<|>|&|"]/g, function (match) {
|
||||
switch (match) {
|
||||
case '<': return '<';
|
||||
case '>': return '>';
|
||||
case '&': return '&';
|
||||
case '"': return '"';
|
||||
case '\'': return ''';
|
||||
default: return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -13,3 +13,6 @@ export const SerializationDisabled = 'Saving results into different format disab
|
||||
*/
|
||||
export const RestoreFeatureName = 'restore';
|
||||
export const BackupFeatureName = 'backup';
|
||||
|
||||
export const MssqlProviderId = 'MSSQL';
|
||||
|
||||
|
||||
@@ -81,6 +81,15 @@
|
||||
content: url("status_info.svg");
|
||||
}
|
||||
|
||||
.vs .icon.help {
|
||||
content: url("help.svg");
|
||||
}
|
||||
|
||||
.vs-dark .icon.help,
|
||||
.hc-black .icon.help {
|
||||
content: url("help_inverse.svg");
|
||||
}
|
||||
|
||||
.vs .icon.success,
|
||||
.vs-dark .icon.success,
|
||||
.hc-black .icon.success {
|
||||
@@ -216,4 +225,19 @@
|
||||
|
||||
.vs .icon.unpin {
|
||||
background: url('unpin.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.small {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.medium {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.large {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
1
src/sql/media/icons/help.svg
Normal file
1
src/sql/media/icons/help.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>help_16x16</title><path d="M10.13.45A8.14,8.14,0,0,1,12,1.23a8,8,0,0,1,1.62,1.21A7.8,7.8,0,0,1,14.91,4a7.59,7.59,0,0,1,.81,1.85,7.6,7.6,0,0,1,0,4.12,7.59,7.59,0,0,1-.81,1.85,7.8,7.8,0,0,1-1.25,1.57A8,8,0,0,1,12,14.62a8.2,8.2,0,0,1-8.08,0,8,8,0,0,1-1.62-1.21,7.8,7.8,0,0,1-1.25-1.57A7.63,7.63,0,0,1,.28,10a7.6,7.6,0,0,1,0-4.12A7.63,7.63,0,0,1,1.09,4,7.8,7.8,0,0,1,2.34,2.44,8,8,0,0,1,4,1.23,8.26,8.26,0,0,1,10.13.45Zm-.27,14a7.17,7.17,0,0,0,1.67-.69,7,7,0,0,0,1.41-1.06,6.75,6.75,0,0,0,1.8-3,6.59,6.59,0,0,0,0-3.6,6.75,6.75,0,0,0-1.8-3,7,7,0,0,0-1.41-1.06,7.17,7.17,0,0,0-1.67-.69,7.21,7.21,0,0,0-3.72,0,7.17,7.17,0,0,0-1.67.69A7,7,0,0,0,3.05,3.13,6.82,6.82,0,0,0,2,4.5a6.72,6.72,0,0,0-.71,1.62,6.59,6.59,0,0,0,0,3.6A6.72,6.72,0,0,0,2,11.35a6.82,6.82,0,0,0,1.09,1.37,7,7,0,0,0,1.41,1.06,7.17,7.17,0,0,0,1.67.69,7.21,7.21,0,0,0,3.72,0ZM9,4a2.75,2.75,0,0,1,.85.56,2.61,2.61,0,0,1,.57.82,2.44,2.44,0,0,1,.21,1,2.08,2.08,0,0,1-.16.85,2.82,2.82,0,0,1-.4.65,4.28,4.28,0,0,1-.51.53c-.18.16-.36.32-.51.48a2.55,2.55,0,0,0-.4.51,1.19,1.19,0,0,0-.16.61v.52H7.46V10a2.09,2.09,0,0,1,.16-.85A2.86,2.86,0,0,1,8,8.5,4.28,4.28,0,0,1,8.54,8c.18-.16.36-.32.51-.48A2.51,2.51,0,0,0,9.45,7a1.18,1.18,0,0,0,.16-.61,1.49,1.49,0,0,0-.13-.61,1.56,1.56,0,0,0-.34-.49A1.63,1.63,0,0,0,8,4.81a1.63,1.63,0,0,0-1.14.45,1.57,1.57,0,0,0-.34.49,1.49,1.49,0,0,0-.13.61H5.32a2.41,2.41,0,0,1,.21-1,2.68,2.68,0,0,1,.58-.82A2.77,2.77,0,0,1,7,4a2.62,2.62,0,0,1,1-.21A2.65,2.65,0,0,1,9,4ZM8.54,12.6H7.46v-1H8.54Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
src/sql/media/icons/help_inverse.svg
Normal file
1
src/sql/media/icons/help_inverse.svg
Normal 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:#fff;}</style></defs><title>help_inverse_16x16</title><path class="cls-1" d="M10.13.45A8.14,8.14,0,0,1,12,1.23a8,8,0,0,1,1.62,1.21A7.8,7.8,0,0,1,14.91,4a7.59,7.59,0,0,1,.81,1.85,7.6,7.6,0,0,1,0,4.12,7.59,7.59,0,0,1-.81,1.85,7.8,7.8,0,0,1-1.25,1.57A8,8,0,0,1,12,14.62a8.2,8.2,0,0,1-8.08,0,8,8,0,0,1-1.62-1.21,7.8,7.8,0,0,1-1.25-1.57A7.63,7.63,0,0,1,.28,10a7.6,7.6,0,0,1,0-4.12A7.63,7.63,0,0,1,1.09,4,7.8,7.8,0,0,1,2.34,2.44,8,8,0,0,1,4,1.23,8.26,8.26,0,0,1,10.13.45Zm-.27,14a7.17,7.17,0,0,0,1.67-.69,7,7,0,0,0,1.41-1.06,6.75,6.75,0,0,0,1.8-3,6.59,6.59,0,0,0,0-3.6,6.75,6.75,0,0,0-1.8-3,7,7,0,0,0-1.41-1.06,7.17,7.17,0,0,0-1.67-.69,7.21,7.21,0,0,0-3.72,0,7.17,7.17,0,0,0-1.67.69A7,7,0,0,0,3.05,3.13,6.82,6.82,0,0,0,2,4.5a6.72,6.72,0,0,0-.71,1.62,6.59,6.59,0,0,0,0,3.6A6.72,6.72,0,0,0,2,11.35a6.82,6.82,0,0,0,1.09,1.37,7,7,0,0,0,1.41,1.06,7.17,7.17,0,0,0,1.67.69,7.21,7.21,0,0,0,3.72,0ZM9,4a2.75,2.75,0,0,1,.85.56,2.61,2.61,0,0,1,.57.82,2.44,2.44,0,0,1,.21,1,2.08,2.08,0,0,1-.16.85,2.82,2.82,0,0,1-.4.65,4.28,4.28,0,0,1-.51.53c-.18.16-.36.32-.51.48a2.55,2.55,0,0,0-.4.51,1.19,1.19,0,0,0-.16.61v.52H7.46V10a2.09,2.09,0,0,1,.16-.85A2.86,2.86,0,0,1,8,8.5,4.28,4.28,0,0,1,8.54,8c.18-.16.36-.32.51-.48A2.51,2.51,0,0,0,9.45,7a1.18,1.18,0,0,0,.16-.61,1.49,1.49,0,0,0-.13-.61,1.56,1.56,0,0,0-.34-.49A1.63,1.63,0,0,0,8,4.81a1.63,1.63,0,0,0-1.14.45,1.57,1.57,0,0,0-.34.49,1.49,1.49,0,0,0-.13.61H5.32a2.41,2.41,0,0,1,.21-1,2.68,2.68,0,0,1,.58-.82A2.77,2.77,0,0,1,7,4a2.62,2.62,0,0,1,1-.21A2.65,2.65,0,0,1,9,4ZM8.54,12.6H7.46v-1H8.54Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1 +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-blue{fill:#1BA1E2;} .icon-white{fill:#FFFFFF;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 8c0-4.418 3.582-8 8-8s8 3.582 8 8-3.582 8-8 8-8-3.582-8-8z" id="outline"/><path class="icon-vs-blue" d="M8 1c-3.865 0-7 3.135-7 7s3.135 7 7 7 7-3.135 7-7-3.135-7-7-7zm1 12h-2v-7h2v7zm0-8h-2v-2h2v2z" id="iconBg"/><path class="icon-white" d="M7 6h2v7h-2v-7zm0-1h2v-2h-2v2z" id="iconFg"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-blue{fill:#1BA1E2;} .icon-white{fill:#FFFFFF;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 8c0-4.418 3.582-8 8-8s8 3.582 8 8-3.582 8-8 8-8-3.582-8-8z" id="outline"/><path class="icon-vs-blue" d="M8 1c-3.865 0-7 3.135-7 7s3.135 7 7 7 7-3.135 7-7-3.135-7-7-7zm1 12h-2v-7h2v7zm0-8h-2v-2h2v2z" id="iconBg"/><path class="icon-white" d="M7 6h2v7h-2v-7zm0-1h2v-2h-2v2z" id="iconFg"/></svg>
|
||||
|
Before Width: | Height: | Size: 627 B After Width: | Height: | Size: 624 B |
@@ -130,8 +130,8 @@ export class AccountDialog extends Modal {
|
||||
|
||||
this._noaccountViewContainer = DOM.$('div.no-account-view');
|
||||
let noAccountTitle = DOM.append(this._noaccountViewContainer, DOM.$('.no-account-view-label'));
|
||||
let noAccountLabel = localize('accountDialog.noAccountLabel', 'There is no linked account. Please add an acount.');
|
||||
noAccountTitle.innerHTML = noAccountLabel;
|
||||
let noAccountLabel = localize('accountDialog.noAccountLabel', 'There is no linked account. Please add an account.');
|
||||
noAccountTitle.innerText = noAccountLabel;
|
||||
|
||||
// Show the add account button for the first provider
|
||||
// Todo: If we have more than 1 provider, need to show all add account buttons for all providers
|
||||
|
||||
@@ -100,7 +100,7 @@ export class AutoOAuthDialog extends Modal {
|
||||
let inputBox: InputBox;
|
||||
container.div({ class: 'dialog-input-section' }, (inputContainer) => {
|
||||
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
labelContainer.text(label);
|
||||
});
|
||||
|
||||
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
|
||||
|
||||
@@ -145,7 +145,7 @@ export class FirewallRuleDialog extends Modal {
|
||||
subnetIPRangeSection = subnetIPRangeContainer.getHTMLElement();
|
||||
subnetIPRangeContainer.div({ 'class': 'dialog-input-section' }, (inputContainer) => {
|
||||
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
|
||||
labelContainer.innerHtml(LocalizedStrings.FROM);
|
||||
labelContainer.text(LocalizedStrings.FROM);
|
||||
});
|
||||
|
||||
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
|
||||
@@ -155,7 +155,7 @@ export class FirewallRuleDialog extends Modal {
|
||||
});
|
||||
|
||||
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
|
||||
labelContainer.innerHtml(LocalizedStrings.TO);
|
||||
labelContainer.text(LocalizedStrings.TO);
|
||||
});
|
||||
|
||||
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
|
||||
@@ -234,7 +234,7 @@ export class FirewallRuleDialog extends Modal {
|
||||
className += ' header';
|
||||
}
|
||||
container.div({ 'class': className }, (labelContainer) => {
|
||||
labelContainer.innerHtml(content);
|
||||
labelContainer.text(content);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
import { NgModule, Inject, forwardRef, ApplicationRef, ComponentFactoryResolver, Type } from '@angular/core';
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
|
||||
import { CreateLoginComponent, CREATELOGIN_SELECTOR } from 'sql/parts/admin/security/createLogin.component';
|
||||
import { IBootstrapParams, providerIterator } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { CreateLoginComponent } from 'sql/parts/admin/security/createLogin.component';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
// Connection Dashboard main angular module
|
||||
export const CreateLoginModule = (params: IBootstrapParams, selector: string): Type<any> => {
|
||||
export const CreateLoginModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -24,7 +26,8 @@ export const CreateLoginModule = (params: IBootstrapParams, selector: string): T
|
||||
],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
{ provide: IBootstrapParams, useValue: params }
|
||||
{ provide: IBootstrapParams, useValue: params },
|
||||
...providerIterator(instantiationService)
|
||||
]
|
||||
})
|
||||
class ModuleClass {
|
||||
|
||||
@@ -38,13 +38,13 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
|
||||
let uri: URI = getQueryEditorFileUri(input);
|
||||
if (uri) {
|
||||
const queryResultsInput: QueryResultsInput = instantiationService.createInstance(QueryResultsInput, uri.toString());
|
||||
let queryInput: QueryInput = instantiationService.createInstance(QueryInput, input.getName(), '', input, queryResultsInput, undefined);
|
||||
let queryInput: QueryInput = instantiationService.createInstance(QueryInput, '', input, queryResultsInput, undefined);
|
||||
return queryInput;
|
||||
}
|
||||
|
||||
//QueryPlanInput
|
||||
uri = getQueryPlanEditorUri(input);
|
||||
if(uri) {
|
||||
if (uri) {
|
||||
let queryPlanXml: string = fs.readFileSync(uri.fsPath);
|
||||
let queryPlanInput: QueryPlanInput = instantiationService.createInstance(QueryPlanInput, queryPlanXml, 'aaa', undefined);
|
||||
return queryPlanInput;
|
||||
@@ -60,14 +60,14 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
|
||||
*/
|
||||
export function getSupportedInputResource(input: IEditorInput): URI {
|
||||
if (input instanceof UntitledEditorInput) {
|
||||
let untitledCast: UntitledEditorInput = <UntitledEditorInput> input;
|
||||
let untitledCast: UntitledEditorInput = <UntitledEditorInput>input;
|
||||
if (untitledCast) {
|
||||
return untitledCast.getResource();
|
||||
}
|
||||
}
|
||||
|
||||
if (input instanceof FileEditorInput) {
|
||||
let fileCast: FileEditorInput = <FileEditorInput> input;
|
||||
let fileCast: FileEditorInput = <FileEditorInput>input;
|
||||
if (fileCast) {
|
||||
return fileCast.getResource();
|
||||
}
|
||||
@@ -99,7 +99,7 @@ function getQueryEditorFileUri(input: EditorInput): URI {
|
||||
if (uri) {
|
||||
let isValidUri: boolean = !!uri && !!uri.toString;
|
||||
|
||||
if (isValidUri && (hasFileExtension(sqlFileTypes, input, true) || hasSqlFileMode(input)) ) {
|
||||
if (isValidUri && (hasFileExtension(sqlFileTypes, input, true) || hasSqlFileMode(input))) {
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ function getQueryPlanEditorUri(input: EditorInput): URI {
|
||||
// If this editor is not already of type queryinput
|
||||
if (!(input instanceof QueryPlanInput)) {
|
||||
let uri: URI = getSupportedInputResource(input);
|
||||
if(uri) {
|
||||
if (uri) {
|
||||
if (hasFileExtension(sqlPlanFileTypes, input, false)) {
|
||||
return uri;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ function getQueryPlanEditorUri(input: EditorInput): URI {
|
||||
*/
|
||||
function hasSqlFileMode(input: EditorInput): boolean {
|
||||
if (input instanceof UntitledEditorInput) {
|
||||
let untitledCast: UntitledEditorInput = <UntitledEditorInput> input;
|
||||
let untitledCast: UntitledEditorInput = <UntitledEditorInput>input;
|
||||
return untitledCast && (untitledCast.getModeId() === undefined || untitledCast.getModeId() === sqlModeId);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConnectionProfile } from 'sqlops';
|
||||
|
||||
export class ConnectionContextkey implements IContextKey<IConnectionProfile> {
|
||||
export class ConnectionContextKey implements IContextKey<IConnectionProfile> {
|
||||
|
||||
static Provider = new RawContextKey<string>('connectionProvider', undefined);
|
||||
static Server = new RawContextKey<string>('serverName', undefined);
|
||||
@@ -23,10 +23,10 @@ export class ConnectionContextkey implements IContextKey<IConnectionProfile> {
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
this._providerKey = ConnectionContextkey.Provider.bindTo(contextKeyService);
|
||||
this._serverKey = ConnectionContextkey.Server.bindTo(contextKeyService);
|
||||
this._databaseKey = ConnectionContextkey.Database.bindTo(contextKeyService);
|
||||
this._connectionKey = ConnectionContextkey.Connection.bindTo(contextKeyService);
|
||||
this._providerKey = ConnectionContextKey.Provider.bindTo(contextKeyService);
|
||||
this._serverKey = ConnectionContextKey.Server.bindTo(contextKeyService);
|
||||
this._databaseKey = ConnectionContextKey.Database.bindTo(contextKeyService);
|
||||
this._connectionKey = ConnectionContextKey.Connection.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
set(value: IConnectionProfile) {
|
||||
|
||||
@@ -152,10 +152,12 @@ export interface IConnectionManagementService {
|
||||
|
||||
getAdvancedProperties(): sqlops.ConnectionOption[];
|
||||
|
||||
getConnectionId(connectionProfile: IConnectionProfile): string;
|
||||
getConnectionUri(connectionProfile: IConnectionProfile): string;
|
||||
|
||||
getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string;
|
||||
|
||||
getConnectionUriFromId(connectionId: string): string;
|
||||
|
||||
isConnected(fileUri: string): boolean;
|
||||
|
||||
/**
|
||||
@@ -174,7 +176,7 @@ export interface IConnectionManagementService {
|
||||
|
||||
disconnectEditor(owner: IConnectableInput, force?: boolean): Promise<boolean>;
|
||||
|
||||
disconnect(connection: ConnectionProfile): Promise<void>;
|
||||
disconnect(connection: IConnectionProfile): Promise<void>;
|
||||
|
||||
disconnect(ownerUri: string): Promise<void>;
|
||||
|
||||
@@ -208,7 +210,7 @@ export interface IConnectionManagementService {
|
||||
*/
|
||||
cancelEditorConnection(owner: IConnectableInput): Thenable<boolean>;
|
||||
|
||||
showDashboard(connection: ConnectionProfile): Thenable<boolean>;
|
||||
showDashboard(connection: IConnectionProfile): Thenable<boolean>;
|
||||
|
||||
closeDashboard(uri: string): void;
|
||||
|
||||
@@ -216,7 +218,7 @@ export interface IConnectionManagementService {
|
||||
|
||||
hasRegisteredServers(): boolean;
|
||||
|
||||
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
|
||||
canChangeConnectionConfig(profile: IConnectionProfile, newGroupID: string): boolean;
|
||||
|
||||
getTabColorForUri(uri: string): string;
|
||||
|
||||
@@ -320,12 +322,13 @@ export enum MetadataType {
|
||||
}
|
||||
|
||||
export enum TaskStatus {
|
||||
notStarted = 0,
|
||||
inProgress = 1,
|
||||
succeeded = 2,
|
||||
succeededWithWarning = 3,
|
||||
failed = 4,
|
||||
canceled = 5
|
||||
NotStarted = 0,
|
||||
InProgress = 1,
|
||||
Succeeded = 2,
|
||||
SucceededWithWarning = 3,
|
||||
Failed = 4,
|
||||
Canceled = 5,
|
||||
Canceling = 6
|
||||
}
|
||||
|
||||
export interface IConnectionParams {
|
||||
|
||||
@@ -553,7 +553,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
});
|
||||
}
|
||||
|
||||
public showDashboard(connection: ConnectionProfile): Thenable<boolean> {
|
||||
public showDashboard(connection: IConnectionProfile): Thenable<boolean> {
|
||||
return this.showDashboardForConnectionManagementInfo(connection);
|
||||
}
|
||||
|
||||
@@ -648,6 +648,15 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
return this._connectionStatusManager.getActiveConnectionProfiles();
|
||||
}
|
||||
|
||||
public getConnectionUriFromId(connectionId: string): string {
|
||||
let connection = this.getActiveConnections().find(connection => connection.id === connectionId);
|
||||
if (connection) {
|
||||
return this.getConnectionUri(connection);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> {
|
||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.AddServerGroup);
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
@@ -704,7 +713,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
return false;
|
||||
}
|
||||
|
||||
public getConnectionId(connectionProfile: IConnectionProfile): string {
|
||||
public getConnectionUri(connectionProfile: IConnectionProfile): string {
|
||||
return this._connectionStatusManager.getOriginalOwnerUri(Utils.generateUri(connectionProfile));
|
||||
}
|
||||
|
||||
@@ -716,7 +725,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
*/
|
||||
public getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string {
|
||||
if (this._connectionStatusManager.isDefaultTypeUri(uri)) {
|
||||
return this.getConnectionId(connectionProfile);
|
||||
return this.getConnectionUri(connectionProfile);
|
||||
} else {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ export class ConnectionStore {
|
||||
return connectionProfile;
|
||||
} else {
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ export class ConnectionStore {
|
||||
list.unshift(savedProfile);
|
||||
|
||||
let newList = list.map(c => {
|
||||
let connectionProfile = c ? c.toIConnectionProfile() : undefined;;
|
||||
let connectionProfile = c ? c.toIConnectionProfile() : undefined;
|
||||
return connectionProfile;
|
||||
});
|
||||
return newList.filter(n => n !== undefined);
|
||||
@@ -372,7 +372,7 @@ export class ConnectionStore {
|
||||
});
|
||||
|
||||
let newList = list.map(c => {
|
||||
let connectionProfile = c ? c.toIConnectionProfile() : undefined;;
|
||||
let connectionProfile = c ? c.toIConnectionProfile() : undefined;
|
||||
return connectionProfile;
|
||||
});
|
||||
return newList.filter(n => n !== undefined);
|
||||
|
||||
@@ -21,6 +21,8 @@ export const capabilitiesOptions = 'OPTIONS_METADATA';
|
||||
export const configMaxRecentConnections = 'maxRecentConnections';
|
||||
|
||||
export const mssqlProviderName = 'MSSQL';
|
||||
export const anyProviderName = '*';
|
||||
export const connectionProviderContextKey = 'connectionProvider';
|
||||
|
||||
export const applicationName = 'sqlops';
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export class ConnectionController implements IConnectionComponentController {
|
||||
tempProfile.password = password;
|
||||
tempProfile.groupFullName = '';
|
||||
tempProfile.saveProfile = false;
|
||||
let uri = this._connectionManagementService.getConnectionId(tempProfile);
|
||||
let uri = this._connectionManagementService.getConnectionUri(tempProfile);
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
if (this._databaseCache.has(uri)) {
|
||||
let cachedDatabases: string[] = this._databaseCache.get(uri);
|
||||
|
||||
@@ -280,26 +280,23 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// only create the provider maps first time the dialog gets called
|
||||
let capabilitiesPromise: Promise<void> = Promise.resolve();
|
||||
if (this._providerTypes.length === 0) {
|
||||
entries(this._capabilitiesService.providers).forEach(p => {
|
||||
this._providerTypes.push(p[1].connection.displayName);
|
||||
this._providerNameToDisplayNameMap[p[0]] = p[1].connection.displayName;
|
||||
});
|
||||
}
|
||||
capabilitiesPromise.then(s => {
|
||||
this.updateModelServerCapabilities(model);
|
||||
// If connecting from a query editor set "save connection" to false
|
||||
if (params && params.input && params.connectionType === ConnectionType.editor) {
|
||||
this._model.saveProfile = false;
|
||||
}
|
||||
this.updateModelServerCapabilities(model);
|
||||
// If connecting from a query editor set "save connection" to false
|
||||
if (params && params.input && params.connectionType === ConnectionType.editor) {
|
||||
this._model.saveProfile = false;
|
||||
}
|
||||
|
||||
resolve(this.showDialogWithModel().then(() => {
|
||||
if (connectionResult && connectionResult.errorMessage) {
|
||||
this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.callStack);
|
||||
}
|
||||
}));
|
||||
}, e => reject(e));
|
||||
resolve(this.showDialogWithModel().then(() => {
|
||||
if (connectionResult && connectionResult.errorMessage) {
|
||||
this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.callStack);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -342,9 +339,9 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
if (!platform.isWindows && types.isString(message) && message.toLowerCase().includes('kerberos') && message.toLowerCase().includes('kinit')) {
|
||||
message = [
|
||||
localize('kerberosErrorStart', "Connection failed due to Kerberos error."),
|
||||
localize('kerberosHelpLink', " Help configuring Kerberos is available at ") + helpLink,
|
||||
localize('kerberosKinit', " If you have previously connected you may need to re-run kinit.")
|
||||
].join('<br/>');
|
||||
localize('kerberosHelpLink', "Help configuring Kerberos is available at {0}", helpLink),
|
||||
localize('kerberosKinit', "If you have previously connected you may need to re-run kinit.")
|
||||
].join('\r\n');
|
||||
actions.push(new Action('Kinit', 'Run kinit', null, true, () => {
|
||||
this._connectionDialog.close();
|
||||
this._clipboardService.writeText('kinit\r');
|
||||
|
||||
@@ -262,7 +262,7 @@ export class ConnectionDialogWidget extends Modal {
|
||||
let recentHistoryLabel = localize('recentHistory', 'Recent history');
|
||||
recentConnectionContainer.div({ class: 'recent-titles-container' }, (container) => {
|
||||
container.div({ class: 'connection-history-label' }, (recentTitle) => {
|
||||
recentTitle.innerHtml(recentHistoryLabel);
|
||||
recentTitle.text(recentHistoryLabel);
|
||||
});
|
||||
container.div({ class: 'connection-history-actions' }, (actionsContainer) => {
|
||||
this._actionbar = this._register(new ActionBar(actionsContainer.getHTMLElement(), { animated: false }));
|
||||
@@ -303,7 +303,7 @@ export class ConnectionDialogWidget extends Modal {
|
||||
this._noRecentConnectionBuilder.div({ class: 'connection-recent-content' }, (noRecentConnectionContainer) => {
|
||||
let noRecentHistoryLabel = localize('noRecentConnections', 'No recent connection');
|
||||
noRecentConnectionContainer.div({ class: 'no-recent-connections' }, (noRecentTitle) => {
|
||||
noRecentTitle.innerHtml(noRecentHistoryLabel);
|
||||
noRecentTitle.text(noRecentHistoryLabel);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -335,7 +335,7 @@ export class ConnectionDialogWidget extends Modal {
|
||||
this._noSavedConnectionBuilder.div({ class: 'connection-saved-content' }, (noSavedConnectionContainer) => {
|
||||
let noSavedConnectionLabel = localize('noSavedConnections', 'No saved connection');
|
||||
noSavedConnectionContainer.div({ class: 'no-saved-connections' }, (titleContainer) => {
|
||||
titleContainer.innerHtml(noSavedConnectionLabel);
|
||||
titleContainer.text(noSavedConnectionLabel);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,7 +172,8 @@ export class ConnectionWidget {
|
||||
strictSelection: false,
|
||||
placeholder: this._defaultDatabaseName,
|
||||
maxHeight: 125,
|
||||
ariaLabel: databaseOption.displayName
|
||||
ariaLabel: databaseOption.displayName,
|
||||
actionLabel: localize('toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
|
||||
});
|
||||
|
||||
let serverGroupLabel = localize('serverGroup', 'Server group');
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
border-top-color: transparent;
|
||||
height: calc(100% - 350px);
|
||||
display: block;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.connection-recent, .connection-saved {
|
||||
|
||||
@@ -19,9 +19,9 @@ import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebvi
|
||||
import { MODELVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardModelViewContainer.contribution';
|
||||
import { CONTROLHOST_CONTAINER } from 'sql/parts/dashboard/containers/dashboardControlHostContainer.contribution';
|
||||
import { NAV_SECTION } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution';
|
||||
import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions, IDashboardContainer, registerContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
|
||||
import * as Constants from 'sql/parts/connection/common/constants';
|
||||
|
||||
const dashboardcontainerRegistry = Registry.as<IDashboardContainerRegistry>(DashboardContainerExtensions.dashboardContainerContributions);
|
||||
const containerTypes = [
|
||||
@@ -136,13 +136,17 @@ export function addProvider<T extends { connectionManagementService: SingleConne
|
||||
*/
|
||||
export function addEdition<T extends { connectionManagementService: SingleConnectionManagementService }>(config: WidgetConfig[], collection: DashboardServiceInterface): Array<WidgetConfig> {
|
||||
let connectionInfo: ConnectionManagementInfo = collection.connectionManagementService.connectionInfo;
|
||||
let edition = connectionInfo.serverInfo.engineEditionId;
|
||||
return config.map((item) => {
|
||||
if (item.edition === undefined) {
|
||||
item.edition = edition;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
if (connectionInfo.serverInfo) {
|
||||
let edition = connectionInfo.serverInfo.engineEditionId;
|
||||
return config.map((item) => {
|
||||
if (item.edition === undefined) {
|
||||
item.edition = edition;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,9 +166,11 @@ export function addContext(config: WidgetConfig[], collection: any, context: str
|
||||
* Returns a filtered version of the widgets passed based on edition and provider
|
||||
* @param config widgets to filter
|
||||
*/
|
||||
export function filterConfigs<T extends { when?: string }, K extends { contextKeyService: IContextKeyService }>(config: T[], collection: K): Array<T> {
|
||||
export function filterConfigs<T extends { provider?: string | string[], when?: string }, K extends { contextKeyService: IContextKeyService }>(config: T[], collection: K): Array<T> {
|
||||
return config.filter((item) => {
|
||||
if (!item.when) {
|
||||
if (!hasCompatibleProvider(item.provider, collection.contextKeyService)) {
|
||||
return false;
|
||||
} else if (!item.when) {
|
||||
return true;
|
||||
} else {
|
||||
return collection.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(item.when));
|
||||
@@ -172,6 +178,21 @@ export function filterConfigs<T extends { when?: string }, K extends { contextKe
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the listed providers contain '*' indicating any provider will do, or that they are a match
|
||||
* for the currently scoped 'connectionProvider' context key.
|
||||
*/
|
||||
function hasCompatibleProvider(provider: string | string[], contextKeyService: IContextKeyService): boolean {
|
||||
let isCompatible = true;
|
||||
let connectionProvider = contextKeyService.getContextKeyValue<string>(Constants.connectionProviderContextKey);
|
||||
if (connectionProvider) {
|
||||
let providers = (provider instanceof Array) ? provider : [provider];
|
||||
let matchingProvider = providers.find((p) => p === connectionProvider || p === Constants.anyProviderName);
|
||||
isCompatible = (matchingProvider !== undefined);
|
||||
} // Else there's no connection context so skip the check
|
||||
return isCompatible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered container if it is specified as the key
|
||||
* @param container dashboard container
|
||||
|
||||
@@ -17,11 +17,12 @@ import { IDashboardRegistry, Extensions as DashboardExtensions, IDashboardTab }
|
||||
import { PinUnpinTabAction, AddFeatureTabAction } from './actions';
|
||||
import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { AngularEventType, IAngularEventingService } from 'sql/services/angularEventing/angularEventingService';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { DashboardTab, IConfigModifierCollection } from 'sql/parts/dashboard/common/interfaces';
|
||||
import * as dashboardHelper from 'sql/parts/dashboard/common/dashboardHelper';
|
||||
import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import * as Constants from 'sql/parts/connection/common/constants';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as types from 'vs/base/common/types';
|
||||
@@ -38,16 +39,11 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
const dashboardRegistry = Registry.as<IDashboardRegistry>(DashboardExtensions.DashboardContributions);
|
||||
|
||||
interface IConfigModifierCollection {
|
||||
connectionManagementService: SingleConnectionManagementService;
|
||||
contextKeyService: IContextKeyService;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-page',
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/common/dashboardPage.component.html'))
|
||||
})
|
||||
export abstract class DashboardPage extends AngularDisposable {
|
||||
export abstract class DashboardPage extends AngularDisposable implements IConfigModifierCollection {
|
||||
|
||||
protected tabs: Array<TabConfig> = [];
|
||||
|
||||
@@ -137,22 +133,11 @@ export abstract class DashboardPage extends AngularDisposable {
|
||||
this._tabsDispose.forEach(i => i.dispose());
|
||||
this._tabsDispose = [];
|
||||
|
||||
// Create home tab
|
||||
let homeTab: TabConfig = {
|
||||
id: 'homeTab',
|
||||
publisher: undefined,
|
||||
title: this.homeTabTitle,
|
||||
container: { 'widgets-container': homeWidgets },
|
||||
context: this.context,
|
||||
originalConfig: this._originalConfig,
|
||||
editable: true,
|
||||
canClose: false,
|
||||
actions: []
|
||||
};
|
||||
this.addNewTab(homeTab);
|
||||
|
||||
let allTabs = dashboardHelper.filterConfigs(dashboardRegistry.tabs, this);
|
||||
|
||||
// Before separating tabs into pinned / shown, ensure that the home tab is always set up as expected
|
||||
allTabs = this.setAndRemoveHomeTab(allTabs, homeWidgets);
|
||||
|
||||
// Load tab setting configs
|
||||
this._tabSettingConfigs = this.dashboardService.getSettings<Array<TabSettingConfig>>([this.context, 'tabs'].join('.'));
|
||||
|
||||
@@ -199,6 +184,32 @@ export abstract class DashboardPage extends AngularDisposable {
|
||||
}));
|
||||
}
|
||||
|
||||
private setAndRemoveHomeTab(allTabs: IDashboardTab[], homeWidgets: WidgetConfig[]): IDashboardTab[] {
|
||||
let homeTabConfig: TabConfig = {
|
||||
id: 'homeTab',
|
||||
provider: Constants.anyProviderName,
|
||||
publisher: undefined,
|
||||
title: this.homeTabTitle,
|
||||
container: { 'widgets-container': homeWidgets },
|
||||
context: this.context,
|
||||
originalConfig: this._originalConfig,
|
||||
editable: true,
|
||||
canClose: false,
|
||||
actions: []
|
||||
};
|
||||
|
||||
let homeTabIndex = allTabs.findIndex((tab) => tab.isHomeTab === true);
|
||||
if (homeTabIndex !== undefined && homeTabIndex > -1) {
|
||||
// Have a tab: get its information and copy over to the home tab definition
|
||||
let homeTab = allTabs.splice(homeTabIndex, 1)[0];
|
||||
let tabConfig = this.initTabComponents(homeTab);
|
||||
homeTabConfig.id = tabConfig.id;
|
||||
homeTabConfig.container = tabConfig.container;
|
||||
}
|
||||
this.addNewTab(homeTabConfig);
|
||||
return allTabs;
|
||||
}
|
||||
|
||||
private rewriteConfig(): void {
|
||||
let writeableConfig = objects.deepClone(this._tabSettingConfigs);
|
||||
|
||||
@@ -208,35 +219,12 @@ export abstract class DashboardPage extends AngularDisposable {
|
||||
|
||||
private loadNewTabs(dashboardTabs: IDashboardTab[], openLastTab: boolean = false) {
|
||||
if (dashboardTabs && dashboardTabs.length > 0) {
|
||||
let selectedTabs = dashboardTabs.map(v => {
|
||||
let containerResult = dashboardHelper.getDashboardContainer(v.container);
|
||||
if (!containerResult.result) {
|
||||
return { id: v.id, title: v.title, container: { 'error-container': undefined }, alwaysShow: v.alwaysShow };
|
||||
}
|
||||
|
||||
let key = Object.keys(containerResult.container)[0];
|
||||
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
|
||||
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
|
||||
this._configModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs, this.dashboardService, this.context]);
|
||||
});
|
||||
this._gridModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs]);
|
||||
});
|
||||
if (key === WIDGETS_CONTAINER) {
|
||||
return { id: v.id, title: v.title, container: { 'widgets-container': configs }, alwaysShow: v.alwaysShow };
|
||||
|
||||
} else {
|
||||
return { id: v.id, title: v.title, container: { 'grid-container': configs }, alwaysShow: v.alwaysShow };
|
||||
}
|
||||
}
|
||||
return { id: v.id, title: v.title, container: containerResult.container, alwaysShow: v.alwaysShow };
|
||||
}).map(v => {
|
||||
let selectedTabs = dashboardTabs.map(v => this.initTabComponents(v)).map(v => {
|
||||
let actions = [];
|
||||
let tabConfig = this._tabSettingConfigs.find(i => i.tabId === v.id);
|
||||
let tabSettingConfig = this._tabSettingConfigs.find(i => i.tabId === v.id);
|
||||
let isPinned = false;
|
||||
if (tabConfig) {
|
||||
isPinned = tabConfig.isPinned;
|
||||
if (tabSettingConfig) {
|
||||
isPinned = tabSettingConfig.isPinned;
|
||||
} else if (v.alwaysShow) {
|
||||
isPinned = true;
|
||||
}
|
||||
@@ -261,6 +249,30 @@ export abstract class DashboardPage extends AngularDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private initTabComponents(value: IDashboardTab): { id: string; title: string; container: object; alwaysShow: boolean; } {
|
||||
let containerResult = dashboardHelper.getDashboardContainer(value.container);
|
||||
if (!containerResult.result) {
|
||||
return { id: value.id, title: value.title, container: { 'error-container': undefined }, alwaysShow: value.alwaysShow };
|
||||
}
|
||||
let key = Object.keys(containerResult.container)[0];
|
||||
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
|
||||
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
|
||||
this._configModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs, this, this.context]);
|
||||
});
|
||||
this._gridModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs]);
|
||||
});
|
||||
if (key === WIDGETS_CONTAINER) {
|
||||
return { id: value.id, title: value.title, container: { 'widgets-container': configs }, alwaysShow: value.alwaysShow };
|
||||
}
|
||||
else {
|
||||
return { id: value.id, title: value.title, container: { 'grid-container': configs }, alwaysShow: value.alwaysShow };
|
||||
}
|
||||
}
|
||||
return { id: value.id, title: value.title, container: containerResult.container, alwaysShow: value.alwaysShow };
|
||||
}
|
||||
|
||||
private getContentType(tab: TabConfig): string {
|
||||
return tab.container ? Object.keys(tab.container)[0] : '';
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
import 'vs/css!./dashboardPanel';
|
||||
|
||||
import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_ACTIVE_BORDER, TAB_INACTIVE_BACKGROUND, TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import {
|
||||
TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_ACTIVE_BORDER, TAB_INACTIVE_BACKGROUND,
|
||||
TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER, EDITOR_GROUP_BORDER
|
||||
} from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
@@ -98,4 +101,14 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const divider = theme.getColor(EDITOR_GROUP_BORDER);
|
||||
if (divider) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
import * as Constants from 'sql/parts/connection/common/constants';
|
||||
import { registerTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { NAV_SECTION, validateNavSectionContributionAndRegisterIcon } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution';
|
||||
@@ -17,9 +18,11 @@ export interface IDashboardTabContrib {
|
||||
id: string;
|
||||
title: string;
|
||||
container: object;
|
||||
provider: string | string[];
|
||||
when?: string;
|
||||
description?: string;
|
||||
alwaysShow?: boolean;
|
||||
isHomeTab?: boolean;
|
||||
}
|
||||
|
||||
const tabSchema: IJSONSchema = {
|
||||
@@ -41,6 +44,11 @@ const tabSchema: IJSONSchema = {
|
||||
description: localize('sqlops.extension.contributes.tab.when', 'Condition which must be true to show this item'),
|
||||
type: 'string'
|
||||
},
|
||||
provider: {
|
||||
description: localize('sqlops.extension.contributes.tab.provider', 'Defines the connection types this tab is compatible with. Defaults to "MSSQL" if not set'),
|
||||
type: ['string', 'array']
|
||||
|
||||
},
|
||||
container: {
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.container', "The container that will be displayed in this tab."),
|
||||
type: 'object',
|
||||
@@ -49,6 +57,10 @@ const tabSchema: IJSONSchema = {
|
||||
alwaysShow: {
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.alwaysShow', "Whether or not this tab should always be shown or only when the user adds it."),
|
||||
type: 'boolean'
|
||||
},
|
||||
isHomeTab: {
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.isHomeTab', "Whether or not this tab should be used as the Home tab for a connection type."),
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -67,7 +79,7 @@ const tabContributionSchema: IJSONSchema = {
|
||||
ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabContrib[]>('dashboard.tabs', [], tabContributionSchema).setHandler(extensions => {
|
||||
|
||||
function handleCommand(tab: IDashboardTabContrib, extension: IExtensionPointUser<any>) {
|
||||
let { description, container, title, when, id, alwaysShow } = tab;
|
||||
let { description, container, provider, title, when, id, alwaysShow, isHomeTab } = tab;
|
||||
|
||||
// If always show is not specified, set it to true by default.
|
||||
if (!types.isBoolean(alwaysShow)) {
|
||||
@@ -88,6 +100,13 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabCo
|
||||
return;
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
// Use a default. Consider warning extension developers about this in the future if in development mode
|
||||
provider = Constants.mssqlProviderName;
|
||||
// Cannot be a home tab if it did not specify a provider
|
||||
isHomeTab = false;
|
||||
}
|
||||
|
||||
if (Object.keys(container).length !== 1) {
|
||||
extension.collector.error(localize('dashboardTab.contribution.moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space'));
|
||||
return;
|
||||
@@ -110,7 +129,7 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabCo
|
||||
}
|
||||
|
||||
if (result) {
|
||||
registerTab({ description, title, container, when, id, alwaysShow, publisher });
|
||||
registerTab({ description, title, container, provider, when, id, alwaysShow, publisher, isHomeTab });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@ import { OnDestroy } from '@angular/core';
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
|
||||
|
||||
export enum Conditional {
|
||||
'equals',
|
||||
@@ -30,23 +32,16 @@ export abstract class DashboardTab extends TabChild implements OnDestroy {
|
||||
public enableEdit(): void {
|
||||
// no op
|
||||
}
|
||||
|
||||
private _toDispose: IDisposable[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(t: T): T {
|
||||
this._toDispose.push(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IConfigModifierCollection {
|
||||
connectionManagementService: SingleConnectionManagementService;
|
||||
contextKeyService: IContextKeyService;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export class DashboardErrorContainer extends DashboardTab implements AfterViewIn
|
||||
|
||||
ngAfterViewInit() {
|
||||
let errorMessage = this._errorMessageContainer.nativeElement as HTMLElement;
|
||||
errorMessage.innerHTML = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title);
|
||||
errorMessage.innerText = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
|
||||
@@ -5,28 +5,27 @@
|
||||
|
||||
import 'vs/css!./dashboardNavSection';
|
||||
|
||||
import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef, EventEmitter, OnChanges, AfterContentInit } from '@angular/core';
|
||||
import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef, OnChanges, AfterContentInit } from '@angular/core';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { CommonServiceInterface, SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { WidgetConfig, TabConfig, NavSectionConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
|
||||
import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { DashboardTab, IConfigModifierCollection } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
import * as dashboardHelper from 'sql/parts/dashboard/common/dashboardHelper';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-nav-section',
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardNavSection) }],
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/containers/dashboardNavSection.component.html'))
|
||||
})
|
||||
export class DashboardNavSection extends DashboardTab implements OnDestroy, OnChanges, AfterContentInit {
|
||||
export class DashboardNavSection extends DashboardTab implements OnDestroy, OnChanges, AfterContentInit, IConfigModifierCollection {
|
||||
@Input() private tab: TabConfig;
|
||||
protected tabs: Array<TabConfig> = [];
|
||||
private _onResize = new Emitter<void>();
|
||||
@@ -38,7 +37,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
};
|
||||
|
||||
// a set of config modifiers
|
||||
private readonly _configModifiers: Array<(item: Array<WidgetConfig>, dashboardServer: DashboardServiceInterface, context: string) => Array<WidgetConfig>> = [
|
||||
private readonly _configModifiers: Array<(item: Array<WidgetConfig>, collection: IConfigModifierCollection, context: string) => Array<WidgetConfig>> = [
|
||||
dashboardHelper.removeEmpty,
|
||||
dashboardHelper.initExtensionConfigs,
|
||||
dashboardHelper.addProvider,
|
||||
@@ -103,7 +102,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
|
||||
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
|
||||
this._configModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs, this.dashboardService, this.tab.context]);
|
||||
configs = cb.apply(this, [configs, this, this.tab.context]);
|
||||
});
|
||||
this._gridModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs]);
|
||||
@@ -169,4 +168,12 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get connectionManagementService(): SingleConnectionManagementService {
|
||||
return this.dashboardService.connectionManagementService;
|
||||
}
|
||||
|
||||
public get contextKeyService(): IContextKeyService {
|
||||
return this.dashboardService.scopedContextKeyService;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Inject, NgModule, forwardRef, ApplicationRef, ComponentFactoryResolver, NgModuleRef, NgModuleFactory } from '@angular/core';
|
||||
import { Inject, NgModule, forwardRef, ApplicationRef, ComponentFactoryResolver } from '@angular/core';
|
||||
import { CommonModule, APP_BASE_HREF } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule, Routes, UrlSerializer, Router, NavigationEnd } from '@angular/router';
|
||||
@@ -14,7 +14,7 @@ import { ChartsModule } from 'ng2-charts/ng2-charts';
|
||||
import CustomUrlSerializer from 'sql/common/urlSerializer';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
||||
import { Extensions as ComponentExtensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
@@ -32,7 +32,7 @@ import { CommonServiceInterface } from 'sql/services/common/commonServiceInterfa
|
||||
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
|
||||
|
||||
/* Base Components */
|
||||
import { DashboardComponent, DASHBOARD_SELECTOR } from 'sql/parts/dashboard/dashboard.component';
|
||||
import { DashboardComponent } from 'sql/parts/dashboard/dashboard.component';
|
||||
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
|
||||
import { DashboardWidgetContainer } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.component';
|
||||
import { DashboardGridContainer } from 'sql/parts/dashboard/containers/dashboardGridContainer.component';
|
||||
@@ -51,17 +51,20 @@ import { ControlHostContent } from 'sql/parts/dashboard/contents/controlHostCont
|
||||
import { DashboardControlHostContainer } from 'sql/parts/dashboard/containers/dashboardControlHostContainer.component';
|
||||
import { JobsViewComponent } from 'sql/parts/jobManagement/views/jobsView.component';
|
||||
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
|
||||
import { AlertsViewComponent } from 'sql/parts/jobManagement/views/alertsView.component';
|
||||
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
|
||||
import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsView.component';
|
||||
import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
|
||||
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editabledropdown.component';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
|
||||
|
||||
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
|
||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
|
||||
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper, Checkbox,
|
||||
SelectBox,
|
||||
InputBox,];
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, AlertsViewComponent, ProxiesViewComponent, OperatorsViewComponent,
|
||||
DashboardModelViewContainer, ModelComponentWrapper, Checkbox, EditableDropDown, SelectBox, InputBox,];
|
||||
|
||||
/* Panel */
|
||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||
@@ -80,7 +83,8 @@ import { ExplorerWidget } from 'sql/parts/dashboard/widgets/explorer/explorerWid
|
||||
import { TasksWidget } from 'sql/parts/dashboard/widgets/tasks/tasksWidget.component';
|
||||
import { InsightsWidget } from 'sql/parts/dashboard/widgets/insights/insightsWidget.component';
|
||||
import { WebviewWidget } from 'sql/parts/dashboard/widgets/webview/webviewWidget.component';
|
||||
import { JobStepsViewComponent } from '../jobManagement/views/jobStepsView.component';
|
||||
import { JobStepsViewComponent } from 'sql/parts/jobManagement/views/jobStepsView.component';
|
||||
import { IInstantiationService, _util } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
let widgetComponents = [
|
||||
PropertiesWidgetComponent,
|
||||
@@ -109,7 +113,7 @@ const appRoutes: Routes = [
|
||||
];
|
||||
|
||||
// Connection Dashboard main angular module
|
||||
export const DashboardModule = (params, selector: string): any => {
|
||||
export const DashboardModule = (params, selector: string, instantiationService: IInstantiationService): any => {
|
||||
@NgModule({
|
||||
declarations: [
|
||||
...baseComponents,
|
||||
@@ -140,31 +144,31 @@ export const DashboardModule = (params, selector: string): any => {
|
||||
{ provide: IBreadcrumbService, useClass: BreadcrumbService },
|
||||
{ provide: CommonServiceInterface, useClass: DashboardServiceInterface },
|
||||
{ provide: UrlSerializer, useClass: CustomUrlSerializer },
|
||||
{ provide: IBootstrapParams, useValue: params }
|
||||
{ provide: IBootstrapParams, useValue: params },
|
||||
{ provide: ISelector, useValue: selector },
|
||||
...providerIterator(instantiationService)
|
||||
]
|
||||
})
|
||||
class ModuleClass {
|
||||
private _bootstrap: DashboardServiceInterface;
|
||||
private navigations = 0;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) bootstrap: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => Router)) private _router: Router,
|
||||
@Inject(ITelemetryService) private telemetryService: ITelemetryService
|
||||
@Inject(ITelemetryService) private telemetryService: ITelemetryService,
|
||||
@Inject(ISelector) private selector: string
|
||||
) {
|
||||
this._bootstrap = bootstrap as DashboardServiceInterface;
|
||||
}
|
||||
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
const factory = this._resolver.resolveComponentFactory(DashboardComponent);
|
||||
this._bootstrap.selector = selector;
|
||||
(<any>factory).factory.selector = selector;
|
||||
(<any>factory).factory.selector = this.selector;
|
||||
appRef.bootstrap(factory);
|
||||
|
||||
this._router.events.subscribe(e => {
|
||||
if (e instanceof NavigationEnd) {
|
||||
this._bootstrap.handlePageNavigation();
|
||||
this.navigations++;
|
||||
TelemetryUtils.addTelemetry(this.telemetryService, TelemetryKeys.DashboardNavigated, {
|
||||
numberOfNavigations: this._bootstrap.getNumberOfPageNavigations(),
|
||||
numberOfNavigations: this.navigations,
|
||||
routeUrl: e.url
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user