Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9baee1c22c | ||
|
|
8cb67b4f9d | ||
|
|
0cd47bc328 | ||
|
|
07fb58d5e1 | ||
|
|
2d80d5e611 | ||
|
|
4bd63b615b | ||
|
|
335f667507 | ||
|
|
1819036d7d | ||
|
|
4f76f116ac | ||
|
|
1eba7c7d2a | ||
|
|
83234dd52c | ||
|
|
bae23b7fce | ||
|
|
3db61eaa82 | ||
|
|
5cf85a0361 | ||
|
|
ffe27f5bde | ||
|
|
78bcd9d54c | ||
|
|
1a9797f0ff | ||
|
|
0de94ff8a4 | ||
|
|
472233d9a7 | ||
|
|
f69e31b0d5 | ||
|
|
60b696cc31 | ||
|
|
549037f744 | ||
|
|
ca5e1e6133 | ||
|
|
3e3ff163db |
38
extensions/agent/client/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;
|
||||
}
|
||||
}
|
||||
25
extensions/agent/client/src/data/createAlertData.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
export class CreateAlertData {
|
||||
public ownerUri: string;
|
||||
private _alert: sqlops.AgentAlertInfo;
|
||||
|
||||
constructor(ownerUri:string) {
|
||||
this.ownerUri = ownerUri;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
|
||||
}
|
||||
|
||||
public async save() {
|
||||
}
|
||||
}
|
||||
150
extensions/agent/client/src/data/createJobData.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
export class CreateJobData {
|
||||
|
||||
private readonly JobCompletionActionCondition_Always: string = 'When the job completes';
|
||||
private readonly JobCompletionActionCondition_OnFailure: string = 'When the job fails';
|
||||
private readonly JobCompletionActionCondition_OnSuccess: string = 'When the job succeeds';
|
||||
|
||||
// Error Messages
|
||||
private readonly CreateJobErrorMessage_NameIsEmpty = 'Job name must be provided';
|
||||
|
||||
private _ownerUri: string;
|
||||
private _jobCategories: string[];
|
||||
private _operators: string[];
|
||||
private _agentService: sqlops.AgentServicesProvider;
|
||||
private _defaultOwner: string;
|
||||
private _jobCompletionActionConditions: sqlops.CategoryValue[];
|
||||
|
||||
public name: 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) {
|
||||
this._ownerUri = ownerUri;
|
||||
}
|
||||
|
||||
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() {
|
||||
await this._agentService.createJob(this.ownerUri, {
|
||||
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: ''
|
||||
}).then(result => {
|
||||
if (!result.success) {
|
||||
console.info(result.errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
extensions/agent/client/src/data/createScheduleData.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
export class CreateScheduleData {
|
||||
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() {
|
||||
}
|
||||
}
|
||||
71
extensions/agent/client/src/data/createStepData.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
export class CreateStepData {
|
||||
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 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
29
extensions/agent/client/src/data/pickScheduleData.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
export class PickScheduleData {
|
||||
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() {
|
||||
}
|
||||
}
|
||||
149
extensions/agent/client/src/dialogs/createAlertDialog.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { CreateAlertData } from '../data/createAlertData';
|
||||
|
||||
export class CreateAlertDialog {
|
||||
|
||||
// Top level
|
||||
private readonly DialogTitle: string = 'Create Alert';
|
||||
private readonly OkButtonText: string = 'OK';
|
||||
private readonly CancelButtonText: string = 'Cancel';
|
||||
private readonly GeneralTabText: string = 'Response';
|
||||
private readonly ResponseTabText: string = 'Steps';
|
||||
private readonly OptionsTabText: string = 'Options';
|
||||
private readonly HistoryTabText: string = 'History';
|
||||
|
||||
// General tab strings
|
||||
private readonly NameTextBoxLabel: string = 'Name';
|
||||
|
||||
// Response tab strings
|
||||
private readonly ExecuteJobTextBoxLabel: string = 'Execute Job';
|
||||
|
||||
// Options tab strings
|
||||
private readonly AdditionalMessageTextBoxLabel: string = 'Additional notification message to send';
|
||||
|
||||
// History tab strings
|
||||
private readonly ResetCountTextBoxLabel: string = 'Reset Count';
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private responseTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private optionsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private historyTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private schedulesTable: sqlops.TableComponent;
|
||||
|
||||
// General tab controls
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
// Response tab controls
|
||||
private executeJobTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
// Options tab controls
|
||||
private additionalMessageTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
// History tab controls
|
||||
private resetCountTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
private model: CreateAlertData;
|
||||
|
||||
private _onSuccess: vscode.EventEmitter<CreateAlertData> = new vscode.EventEmitter<CreateAlertData>();
|
||||
public readonly onSuccess: vscode.Event<CreateAlertData> = this._onSuccess.event;
|
||||
|
||||
constructor(ownerUri: string) {
|
||||
this.model = new CreateAlertData(ownerUri);
|
||||
}
|
||||
|
||||
public async showDialog() {
|
||||
await this.model.initialize();
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
|
||||
this.responseTab = sqlops.window.modelviewdialog.createTab(this.ResponseTabText);
|
||||
this.optionsTab = sqlops.window.modelviewdialog.createTab(this.OptionsTabText);
|
||||
this.historyTab = sqlops.window.modelviewdialog.createTab(this.HistoryTabText);
|
||||
|
||||
this.initializeGeneralTab();
|
||||
this.initializeResponseTab();
|
||||
this.initializeOptionsTab();
|
||||
this.initializeHistoryTab();
|
||||
|
||||
this.dialog.content = [this.generalTab, this.responseTab, this.optionsTab, this.historyTab];
|
||||
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 initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
this.nameTextBox = view.modelBuilder.inputBox().component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: this.NameTextBoxLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeResponseTab() {
|
||||
this.responseTab.registerContent(async view => {
|
||||
this.executeJobTextBox = view.modelBuilder.inputBox().component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.executeJobTextBox,
|
||||
title: this.ExecuteJobTextBoxLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeOptionsTab() {
|
||||
this.optionsTab.registerContent(async view => {
|
||||
this.additionalMessageTextBox = view.modelBuilder.inputBox().component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.additionalMessageTextBox,
|
||||
title: this.AdditionalMessageTextBoxLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeHistoryTab() {
|
||||
this.historyTab.registerContent(async view => {
|
||||
this.resetCountTextBox = view.modelBuilder.inputBox().component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.resetCountTextBox,
|
||||
title: this.ResetCountTextBoxLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
this.updateModel();
|
||||
await this.model.save();
|
||||
this._onSuccess.fire(this.model);
|
||||
}
|
||||
|
||||
private async cancel() {
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
}
|
||||
}
|
||||
464
extensions/agent/client/src/dialogs/createJobDialog.ts
Normal file
@@ -0,0 +1,464 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { CreateJobData } from '../data/createJobData';
|
||||
import { CreateStepDialog } from './createStepDialog';
|
||||
import { PickScheduleDialog } from './pickScheduleDialog';
|
||||
import { CreateAlertDialog } from './createAlertDialog';
|
||||
|
||||
export class CreateJobDialog {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
private readonly DialogTitle: string = 'New Job';
|
||||
private readonly OkButtonText: string = 'OK';
|
||||
private readonly CancelButtonText: string = 'Cancel';
|
||||
private readonly GeneralTabText: string = 'General';
|
||||
private readonly StepsTabText: string = 'Steps';
|
||||
private readonly SchedulesTabText: string = 'Schedules';
|
||||
private readonly AlertsTabText: string = 'Alerts';
|
||||
private readonly NotificationsTabText: string = 'Notifications';
|
||||
|
||||
// General tab strings
|
||||
private readonly NameTextBoxLabel: string = 'Name';
|
||||
private readonly OwnerTextBoxLabel: string = 'Owner';
|
||||
private readonly CategoryDropdownLabel: string = 'Category';
|
||||
private readonly DescriptionTextBoxLabel: string = 'Description';
|
||||
private readonly EnabledCheckboxLabel: string = 'Enabled';
|
||||
|
||||
// Steps tab strings
|
||||
private readonly JobStepsTopLabelString: string = 'Job step list';
|
||||
private readonly StepsTable_StepColumnString: string = 'Step';
|
||||
private readonly StepsTable_NameColumnString: string = 'Name';
|
||||
private readonly StepsTable_TypeColumnString: string = 'Type';
|
||||
private readonly StepsTable_SuccessColumnString: string = 'On Success';
|
||||
private readonly StepsTable_FailureColumnString: string = 'On Failure';
|
||||
private readonly NewStepButtonString: string = 'New...';
|
||||
private readonly InsertStepButtonString: string = 'Insert...';
|
||||
private readonly EditStepButtonString: string = 'Edit';
|
||||
private readonly DeleteStepButtonString: string = 'Delete';
|
||||
|
||||
// Notifications tab strings
|
||||
private readonly NotificationsTabTopLabelString: string = 'Actions to perform when the job completes';
|
||||
private readonly EmailCheckBoxString: string = 'Email';
|
||||
private readonly PagerCheckBoxString: string = 'Page';
|
||||
private readonly EventLogCheckBoxString: string = 'Write to the Windows Application event log';
|
||||
private readonly DeleteJobCheckBoxString: string = 'Automatically delete job';
|
||||
|
||||
// Schedules tab strings
|
||||
private readonly SchedulesTopLabelString: string = 'Schedules list';
|
||||
private readonly PickScheduleButtonString: string = 'Pick Schedule';
|
||||
|
||||
// Alerts tab strings
|
||||
private readonly AlertsTopLabelString: string = 'Alerts list';
|
||||
private readonly NewAlertButtonString: string = 'New Alert';
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
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 insertStepButton: 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;
|
||||
|
||||
private model: CreateJobData;
|
||||
|
||||
constructor(ownerUri: string) {
|
||||
this.model = new CreateJobData(ownerUri);
|
||||
}
|
||||
|
||||
public async showDialog() {
|
||||
await this.model.initialize();
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
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.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;
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
this.nameTextBox = view.modelBuilder.inputBox().component();
|
||||
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.ownerTextBox.value = this.model.defaultOwner;
|
||||
this.categoryDropdown.values = this.model.jobCategories;
|
||||
this.categoryDropdown.value = this.model.jobCategories[0];
|
||||
this.enabledCheckBox.checked = this.model.enabled;
|
||||
this.descriptionTextBox.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
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: 800
|
||||
}).component();
|
||||
|
||||
this.newStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.NewStepButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.newStepButton.onDidClick((e)=>{
|
||||
let stepDialog = new CreateStepDialog(this.model.ownerUri, '', '', 1, this.model);
|
||||
stepDialog.openNewStepDialog();
|
||||
});
|
||||
|
||||
this.insertStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.InsertStepButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
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.newStepButton, this.insertStepButton, 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: [
|
||||
'Alert Name'
|
||||
],
|
||||
data: [],
|
||||
height: 600,
|
||||
width: 400
|
||||
}).component();
|
||||
|
||||
this.newAlertButton = view.modelBuilder.button().withProperties({
|
||||
label: this.NewAlertButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.newAlertButton.onDidClick((e)=>{
|
||||
let alertDialog = new CreateAlertDialog(this.model.ownerUri);
|
||||
alertDialog.onSuccess((dialogModel) => {
|
||||
});
|
||||
alertDialog.showDialog();
|
||||
});
|
||||
|
||||
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: [
|
||||
'Schedule Name'
|
||||
],
|
||||
data: [],
|
||||
height: 600,
|
||||
width: 400
|
||||
}).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([
|
||||
{
|
||||
component: this.notificationsTabTopLabel,
|
||||
title: ''
|
||||
}, {
|
||||
component: emailContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: pagerContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: eventLogContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: deleteJobContainer,
|
||||
title: ''
|
||||
}]).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'
|
||||
});
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
this.updateModel();
|
||||
await this.model.save();
|
||||
}
|
||||
|
||||
private async cancel() {
|
||||
|
||||
}
|
||||
|
||||
private getActualConditionValue(checkbox: sqlops.CheckBoxComponent, dropdown: sqlops.DropDownComponent): sqlops.JobCompletionActionCondition {
|
||||
return checkbox.checked ? Number(this.getDropdownValue(dropdown)) : sqlops.JobCompletionActionCondition.Never;
|
||||
}
|
||||
|
||||
private getDropdownValue(dropdown: sqlops.DropDownComponent): string {
|
||||
return (typeof dropdown.value === 'string') ? dropdown.value : dropdown.value.name;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
91
extensions/agent/client/src/dialogs/createScheduleDialog.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { CreateScheduleData } from '../data/createScheduleData';
|
||||
|
||||
export class CreateScheduleDialog {
|
||||
|
||||
// Top level
|
||||
private readonly DialogTitle: string = 'New Schedule';
|
||||
private readonly OkButtonText: string = 'OK';
|
||||
private readonly CancelButtonText: string = 'Cancel';
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private schedulesTable: sqlops.TableComponent;
|
||||
|
||||
private model: CreateScheduleData;
|
||||
|
||||
private _onSuccess: vscode.EventEmitter<CreateScheduleData> = new vscode.EventEmitter<CreateScheduleData>();
|
||||
public readonly onSuccess: vscode.Event<CreateScheduleData> = this._onSuccess.event;
|
||||
|
||||
constructor(ownerUri: string) {
|
||||
this.model = new CreateScheduleData(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: [
|
||||
'Schedule Name'
|
||||
],
|
||||
data: [],
|
||||
height: 600,
|
||||
width: 400
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
title: 'Schedules'
|
||||
}]).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];
|
||||
}
|
||||
}
|
||||
}
|
||||
421
extensions/agent/client/src/dialogs/createStepDialog.ts
Normal file
@@ -0,0 +1,421 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { CreateStepData } from '../data/createStepData';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { CreateJobData } from '../data/createJobData';
|
||||
|
||||
export class CreateStepDialog {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
//
|
||||
private static readonly DialogTitle: string = 'New Job Step';
|
||||
private static readonly OkButtonText: string = 'OK';
|
||||
private static readonly CancelButtonText: string = 'Cancel';
|
||||
private static readonly GeneralTabText: string = 'General';
|
||||
private static readonly AdvancedTabText: string = 'Advanced';
|
||||
private static readonly OpenCommandText: string = 'Open...';
|
||||
private static readonly ParseCommandText: string = 'Parse';
|
||||
private static readonly NextButtonText: string = 'Next';
|
||||
private static readonly PreviousButtonText: string = 'Previous';
|
||||
private static readonly SuccessAction: string = 'On success action';
|
||||
private static readonly FailureAction: string = 'On failure action';
|
||||
|
||||
|
||||
// Dropdown options
|
||||
private static readonly TSQLScript: string = 'Transact-SQL script (T-SQL)';
|
||||
private static readonly AgentServiceAccount: string = 'SQL Server Agent Service Account';
|
||||
private static readonly NextStep: string = 'Go to the next step';
|
||||
private static readonly QuitJobReportingSuccess: string = 'Quit the job reporting success';
|
||||
private static readonly QuitJobReportingFailure: string = 'Quit the job reporting failure';
|
||||
|
||||
// UI Components
|
||||
//
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private advancedTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private typeDropdown: sqlops.DropDownComponent;
|
||||
private runAsDropdown: sqlops.DropDownComponent;
|
||||
private databaseDropdown: sqlops.DropDownComponent;
|
||||
private successActionDropdown: sqlops.DropDownComponent;
|
||||
private failureActionDropdown: sqlops.DropDownComponent;
|
||||
private commandTextBox: sqlops.InputBoxComponent;
|
||||
private openButton: sqlops.ButtonComponent;
|
||||
private parseButton: sqlops.ButtonComponent;
|
||||
private nextButton: sqlops.ButtonComponent;
|
||||
private previousButton: sqlops.ButtonComponent;
|
||||
private retryAttemptsBox: sqlops.InputBoxComponent;
|
||||
private retryIntervalBox: sqlops.InputBoxComponent;
|
||||
private appendToExistingFileCheckbox: sqlops.CheckBoxComponent;
|
||||
private logToTableCheckbox: sqlops.CheckBoxComponent;
|
||||
private outputFileNameBox: sqlops.InputBoxComponent;
|
||||
private outputFileBrowserButton: sqlops.ButtonComponent;
|
||||
|
||||
private model: CreateStepData;
|
||||
private ownerUri: string;
|
||||
private jobName: string;
|
||||
private server: string;
|
||||
private stepId: number;
|
||||
|
||||
private jobModel: CreateJobData;
|
||||
|
||||
constructor(
|
||||
ownerUri: string,
|
||||
jobName: string,
|
||||
server: string,
|
||||
stepId: number,
|
||||
jobModel?: CreateJobData
|
||||
) {
|
||||
this.model = new CreateStepData(ownerUri);
|
||||
this.stepId = stepId;
|
||||
this.ownerUri = ownerUri;
|
||||
this.jobName = jobName;
|
||||
this.server = server;
|
||||
this.jobModel = jobModel;
|
||||
}
|
||||
|
||||
private initializeUIComponents() {
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(CreateStepDialog.DialogTitle);
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(CreateStepDialog.GeneralTabText);
|
||||
this.advancedTab = sqlops.window.modelviewdialog.createTab(CreateStepDialog.AdvancedTabText);
|
||||
this.dialog.content = [this.generalTab, this.advancedTab];
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
this.dialog.okButton.label = CreateStepDialog.OkButtonText;
|
||||
this.dialog.cancelButton.label = CreateStepDialog.CancelButtonText;
|
||||
}
|
||||
|
||||
private createCommands(view, queryProvider: sqlops.QueryProvider) {
|
||||
this.openButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: CreateStepDialog.OpenCommandText,
|
||||
width: '80px'
|
||||
}).component();
|
||||
this.parseButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: CreateStepDialog.ParseCommandText,
|
||||
width: '80px'
|
||||
}).component();
|
||||
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: 'The command was successfully parsed.', level: 2};
|
||||
} else if (result && !result.parseable) {
|
||||
this.dialog.message = { text: 'The command failed' };
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.commandTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
height: 300,
|
||||
width: 400,
|
||||
multiline: true,
|
||||
inputType: 'text'
|
||||
})
|
||||
.component();
|
||||
this.nextButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: CreateStepDialog.NextButtonText,
|
||||
enabled: false,
|
||||
width: '80px'
|
||||
}).component();
|
||||
this.previousButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: CreateStepDialog.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.typeDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: CreateStepDialog.TSQLScript,
|
||||
values: [CreateStepDialog.TSQLScript]
|
||||
})
|
||||
.component();
|
||||
this.runAsDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: '',
|
||||
values: ['']
|
||||
})
|
||||
.component();
|
||||
this.runAsDropdown.enabled = false;
|
||||
this.typeDropdown.onValueChanged((type) => {
|
||||
if (type.selected !== CreateStepDialog.TSQLScript) {
|
||||
this.runAsDropdown.value = CreateStepDialog.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: 'Step name'
|
||||
}, {
|
||||
component: this.typeDropdown,
|
||||
title: 'Type'
|
||||
}, {
|
||||
component: this.runAsDropdown,
|
||||
title: 'Run as'
|
||||
}, {
|
||||
component: this.databaseDropdown,
|
||||
title: 'Database'
|
||||
}, {
|
||||
component: this.commandTextBox,
|
||||
title: 'Command',
|
||||
actions: [buttonContainer]
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: 420
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
await view.initializeModel(formWrapper);
|
||||
});
|
||||
}
|
||||
|
||||
private createRunAsUserOptions(view) {
|
||||
let userInputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({ inputType: 'text', width: '100px' }).component();
|
||||
let viewButton = view.modelBuilder.button()
|
||||
.withProperties({ label: '...', width: '20px' }).component();
|
||||
let viewButtonContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({ width: 100, textAlign: 'right' })
|
||||
.withItems([viewButton], { flex: '1 1 50%' }).component();
|
||||
let userInputBoxContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({ width: 200, textAlign: 'left' })
|
||||
.withItems([userInputBox], { flex: '1 1 50%' }).component();
|
||||
let runAsUserContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({ width: 200 })
|
||||
.withItems([userInputBoxContainer, viewButtonContainer], { flex: '1 1 50%' })
|
||||
.component();
|
||||
let runAsUserForm = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: runAsUserContainer,
|
||||
title: 'Run as user'
|
||||
}], { horizontal: true, componentWidth: 200 }).component();
|
||||
return runAsUserForm;
|
||||
}
|
||||
|
||||
private createAdvancedTab() {
|
||||
this.advancedTab.registerContent(async (view) => {
|
||||
this.successActionDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: CreateStepDialog.NextStep,
|
||||
values: [CreateStepDialog.NextStep, CreateStepDialog.QuitJobReportingSuccess, CreateStepDialog.QuitJobReportingFailure]
|
||||
})
|
||||
.component();
|
||||
let retryFlexContainer = this.createRetryCounters(view);
|
||||
this.failureActionDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: CreateStepDialog.QuitJobReportingFailure,
|
||||
values: [CreateStepDialog.QuitJobReportingFailure, CreateStepDialog.NextStep, CreateStepDialog.QuitJobReportingSuccess]
|
||||
})
|
||||
.component();
|
||||
let optionsGroup = this.createTSQLOptions(view);
|
||||
let viewButton = view.modelBuilder.button()
|
||||
.withProperties({ label: 'View', width: '50px' }).component();
|
||||
viewButton.enabled = false;
|
||||
this.logToTableCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: 'Log to table'
|
||||
}).component();
|
||||
let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({ label: 'Append output to existing entry in table' }).component();
|
||||
appendToExistingEntryInTableCheckbox.enabled = false;
|
||||
this.logToTableCheckbox.onChanged(e => {
|
||||
viewButton.enabled = 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, viewButton]).component();
|
||||
let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({ label: 'Include step output in history' }).component();
|
||||
let runAsUserOptions = this.createRunAsUserOptions(view);
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.successActionDropdown,
|
||||
title: CreateStepDialog.SuccessAction
|
||||
}, {
|
||||
component: retryFlexContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.failureActionDropdown,
|
||||
title: CreateStepDialog.FailureAction
|
||||
}, {
|
||||
component: optionsGroup,
|
||||
title: 'Transact-SQL script (T-SQL)'
|
||||
}, {
|
||||
component: logToTableContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: appendCheckboxContainer,
|
||||
title: ' '
|
||||
}, {
|
||||
component: logStepOutputHistoryCheckbox,
|
||||
title: ''
|
||||
}, {
|
||||
component: runAsUserOptions,
|
||||
title: ''
|
||||
}], {
|
||||
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'
|
||||
})
|
||||
.component();
|
||||
this.retryIntervalBox = view.modelBuilder.inputBox()
|
||||
.withValidation(component => component.value >= 0)
|
||||
.withProperties({
|
||||
inputType: 'number'
|
||||
}).component();
|
||||
|
||||
let retryAttemptsContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.retryAttemptsBox,
|
||||
title: 'Retry Attempts'
|
||||
}], {
|
||||
horizontal: false
|
||||
})
|
||||
.component();
|
||||
|
||||
let retryIntervalContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.retryIntervalBox,
|
||||
title: 'Retry Interval (minutes)'
|
||||
}], {
|
||||
horizontal: false
|
||||
})
|
||||
.component();
|
||||
|
||||
let retryFlexContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
}).withItems([retryAttemptsContainer, retryIntervalContainer]).component();
|
||||
return retryFlexContainer;
|
||||
}
|
||||
|
||||
private createTSQLOptions(view) {
|
||||
this.outputFileBrowserButton = view.modelBuilder.button()
|
||||
.withProperties({ width: '20px', label: '...' }).component();
|
||||
this.outputFileNameBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
width: '100px',
|
||||
inputType: 'text'
|
||||
}).component();
|
||||
let outputViewButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
width: '50px',
|
||||
label: 'View'
|
||||
}).component();
|
||||
outputViewButton.enabled = false;
|
||||
let outputButtonContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
textAlign: 'right',
|
||||
width: 120
|
||||
}).withItems([this.outputFileBrowserButton, outputViewButton], { 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: 'Append output to existing file'
|
||||
}).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: 'Output file'
|
||||
}, {
|
||||
component: this.appendToExistingFileCheckbox,
|
||||
title: ''
|
||||
}], { horizontal: true, componentWidth: 200 }).component();
|
||||
return outputFileForm;
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
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.model.retryInterval = +this.retryIntervalBox.value;
|
||||
this.model.failureAction = this.failureActionDropdown.value as string;
|
||||
this.model.outputFileName = this.outputFileNameBox.value;
|
||||
await this.model.save();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
92
extensions/agent/client/src/dialogs/pickScheduleDialog.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { PickScheduleData } from '../data/pickScheduleData';
|
||||
|
||||
export class PickScheduleDialog {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
private readonly DialogTitle: string = 'Job Schedules';
|
||||
private readonly OkButtonText: string = 'OK';
|
||||
private readonly CancelButtonText: string = 'Cancel';
|
||||
private readonly SchedulesTabText: string = '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: [
|
||||
'Schedule Name'
|
||||
],
|
||||
data: [],
|
||||
height: 600,
|
||||
width: 400
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
title: 'Schedules'
|
||||
}]).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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,24 +3,47 @@
|
||||
* 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';
|
||||
import { CreateAlertDialog } from './dialogs/createAlertDialog';
|
||||
import { CreateJobDialog } from './dialogs/createJobDialog';
|
||||
import { CreateStepDialog } from './dialogs/createStepDialog';
|
||||
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _apiWrapper: ApiWrapper;
|
||||
export class MainController {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
|
||||
this._apiWrapper = apiWrapper || new ApiWrapper();
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
console.log('Got: ' + apiWrapper);
|
||||
/**
|
||||
* Activates the extension
|
||||
*/
|
||||
public activate(): void {
|
||||
vscode.commands.registerCommand('agent.openCreateJobDialog', (ownerUri: string) => {
|
||||
let dialog = new CreateJobDialog(ownerUri);
|
||||
dialog.showDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, jobId: string, server: string, stepId: number) => {
|
||||
let dialog = new CreateStepDialog(ownerUri, jobId, server, stepId);
|
||||
dialog.openNewStepDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string) => {
|
||||
let dialog = new PickScheduleDialog(ownerUri);
|
||||
dialog.showDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openCreateAlertDialog', (ownerUri: string) => {
|
||||
let dialog = new CreateAlertDialog(ownerUri);
|
||||
dialog.showDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openCreateOperatorDialog', (ownerUri: string) => {
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openCreateProxyDialog', (ownerUri: string) => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,11 +51,4 @@ export class MainController {
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
|
||||
this._apiWrapper.registerWebviewProvider('data-management-agent', webview => {
|
||||
webview.html = '<div><h1>SQL Agent</h1></div>';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
1
extensions/agent/client/src/typings/ref.d.ts
vendored
@@ -5,4 +5,5 @@
|
||||
|
||||
/// <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'/>
|
||||
3234
extensions/agent/package-lock.json
generated
Normal file
@@ -9,15 +9,12 @@
|
||||
"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"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/sqlopsstudio.git"
|
||||
@@ -29,22 +26,25 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"vscode": "1.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.4.0-alpha.46",
|
||||
"version": "1.5.0-alpha.2",
|
||||
"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,27 @@ export interface DeleteAgentProxyParams {
|
||||
proxy: sqlops.AgentProxyInfo;
|
||||
}
|
||||
|
||||
// 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 +194,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 +262,21 @@ export namespace DeleteAgentProxyRequest {
|
||||
export const type = new RequestType<DeleteAgentProxyParams, sqlops.ResultStatus, void, void>('agent/deleteproxy');
|
||||
}
|
||||
|
||||
// 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 > ------------------------------------
|
||||
|
||||
@@ -135,6 +135,20 @@ export class AgentServicesFeature extends SqlOpsFeature<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 => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Job Step management methods
|
||||
let createJobStep = (ownerUri: string, stepInfo: sqlops.AgentJobStepInfo): Thenable<sqlops.CreateAgentJobStepResult> => {
|
||||
let params: contracts.CreateAgentJobStepParams = {
|
||||
@@ -365,6 +379,67 @@ 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 => 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 => 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 => r,
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return sqlops.dataprotocol.registerAgentServicesProvider({
|
||||
providerId: client.providerId,
|
||||
getJobs,
|
||||
@@ -373,6 +448,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
createJob,
|
||||
updateJob,
|
||||
deleteJob,
|
||||
getJobDefaults,
|
||||
createJobStep,
|
||||
updateJobStep,
|
||||
deleteJobStep,
|
||||
@@ -387,7 +463,11 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
getProxies,
|
||||
createProxy,
|
||||
updateProxy,
|
||||
deleteProxy
|
||||
deleteProxy,
|
||||
getJobSchedules,
|
||||
createJobSchedule,
|
||||
updateJobSchedule,
|
||||
deleteJobSchedule
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"rxjs": "5.4.0",
|
||||
"semver": "4.3.6",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.20",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.22",
|
||||
"spdlog": "0.6.0",
|
||||
"sudo-prompt": "^8.0.0",
|
||||
"svg.js": "^2.2.5",
|
||||
|
||||
@@ -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,29 @@ 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.pages = [page1, page2];
|
||||
wizard.open();
|
||||
}
|
||||
|
||||
private openEditor(): void {
|
||||
let editor = sqlops.workspace.createModelViewEditor('Test Model View');
|
||||
editor.registerContent(async view => {
|
||||
|
||||
@@ -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);
|
||||
@@ -103,4 +104,21 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,4 +216,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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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',
|
||||
@@ -50,3 +52,8 @@ export abstract class DashboardTab extends TabChild implements OnDestroy {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IConfigModifierCollection {
|
||||
connectionManagementService: SingleConnectionManagementService;
|
||||
contextKeyService: IContextKeyService;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,10 @@ 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 { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
|
||||
@@ -59,9 +62,8 @@ 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, SelectBox, InputBox,];
|
||||
|
||||
/* Panel */
|
||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||
|
||||
@@ -96,16 +96,12 @@ let defaultVal = [
|
||||
},
|
||||
{
|
||||
widget: {
|
||||
'backup-history-server-insight': {
|
||||
cacheId: '0c7cba8b-c87a-4bcc-ae54-2f40a5503a90'
|
||||
}
|
||||
'backup-history-server-insight': null
|
||||
}
|
||||
},
|
||||
{
|
||||
widget: {
|
||||
'all-database-size-server-insight': {
|
||||
cacheId: '1d7cba8b-c87a-4bcc-ae54-2f40a5503a90'
|
||||
}
|
||||
'all-database-size-server-insight': null
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -121,8 +117,7 @@ export const serverDashboardTabsSchema: IJSONSchema = {
|
||||
type: ['array'],
|
||||
description: nls.localize('dashboardServerTabs', 'Customizes the Server dashboard tabs'),
|
||||
items: generateDashboardTabSchema('server'),
|
||||
default: [
|
||||
]
|
||||
default: []
|
||||
};
|
||||
|
||||
export const SERVER_DASHBOARD_SETTING = 'dashboard.server.widgets';
|
||||
|
||||
@@ -28,6 +28,7 @@ import { IContextViewService, IContextMenuService } from 'vs/platform/contextvie
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
@Component({
|
||||
selector: 'explorer-widget',
|
||||
@@ -120,6 +121,8 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
|
||||
let currentProfile = this._bootstrap.connectionManagementService.connectionInfo.connectionProfile;
|
||||
this._register(toDisposableSubscription(this._bootstrap.metadataService.databaseNames.subscribe(
|
||||
data => {
|
||||
// Handle the case where there is no metadata service
|
||||
data = data || [];
|
||||
let profileData = data.map(d => {
|
||||
let profile = new ConnectionProfile(this.capabilitiesService, currentProfile);
|
||||
profile.databaseName = d;
|
||||
|
||||
@@ -166,7 +166,6 @@ export class TasksWidget extends DashboardWidget implements IDashboardWidget, On
|
||||
}
|
||||
|
||||
public runTask(task: ICommandAction) {
|
||||
let serverInfo = this._bootstrap.connectionManagementService.connectionInfo.serverInfo;
|
||||
this.commandService.executeCommand(task.id, this._profile);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,17 +4,42 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
|
||||
<panel class="dashboard-panel" [options]="panelOpt">
|
||||
<tab [title]="jobsComponentTitle" class="fullsize" [identifier]="jobsTabIdentifier"
|
||||
[iconClass]="jobsIconClass">
|
||||
<ng-template>
|
||||
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">
|
||||
<jobsview-component></jobsview-component>
|
||||
</div>
|
||||
<div id="historyDiv" class="fullsize" *ngIf="showHistory === true">
|
||||
<jobhistory-component></jobhistory-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
</panel>
|
||||
<div id="agentViewDiv" class="fullsize">
|
||||
<panel class="dashboard-panel" [options]="panelOpt">
|
||||
<tab [title]="jobsComponentTitle" class="fullsize" [identifier]="jobsTabIdentifier"
|
||||
[iconClass]="jobsIconClass">
|
||||
<ng-template>
|
||||
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">
|
||||
<jobsview-component></jobsview-component>
|
||||
</div>
|
||||
<div id="historyDiv" class="fullsize" *ngIf="showHistory === true">
|
||||
<jobhistory-component></jobhistory-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
<tab [title]="alertsComponentTitle" class="fullsize" [identifier]="alertsTabIdentifier"
|
||||
[iconClass]="alertsIconClass">
|
||||
<ng-template>
|
||||
<div id="alertsDiv" class="fullsize">
|
||||
<jobalertsview-component></jobalertsview-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
<tab [title]="operatorsComponentTitle" class="fullsize" [identifier]="operatorsTabIdentifier"
|
||||
[iconClass]="operatorsIconClass">
|
||||
<ng-template>
|
||||
<div id="operatorsDiv" class="fullsize">
|
||||
<joboperatorsview-component></joboperatorsview-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
<tab [title]="proxiesComponentTitle" class="fullsize" [identifier]="proxiesTabIdentifier"
|
||||
[iconClass]="proxiesIconClass">
|
||||
<ng-template>
|
||||
<div id="proxiesDiv" class="fullsize">
|
||||
<jobproxiesview-component></jobproxiesview-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
</panel>
|
||||
</div>
|
||||
@@ -33,6 +33,10 @@ export class AgentViewComponent {
|
||||
|
||||
// tslint:disable:no-unused-variable
|
||||
private readonly jobsComponentTitle: string = nls.localize('jobview.Jobs', "Jobs");
|
||||
private readonly alertsComponentTitle: string = nls.localize('jobview.Alerts', "Alerts");
|
||||
private readonly proxiesComponentTitle: string = nls.localize('jobview.Proxies', "Proxies");
|
||||
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operators', "Operators");
|
||||
|
||||
private _showHistory: boolean = false;
|
||||
private _jobId: string = null;
|
||||
private _agentJobInfo: AgentJobInfo = null;
|
||||
@@ -40,6 +44,9 @@ export class AgentViewComponent {
|
||||
private _expanded: Map<string, string>;
|
||||
|
||||
public jobsIconClass: string = 'jobsview-icon';
|
||||
public alertsIconClass: string = 'alertsview-icon';
|
||||
public proxiesIconClass: string = 'proxiesview-icon';
|
||||
public operatorsIconClass: string = 'operatorsview-icon';
|
||||
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
private readonly panelOpt: IPanelOptions = {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { JobCacheObject } from './jobManagementService';
|
||||
|
||||
export const SERVICE_ID = 'jobManagementService';
|
||||
@@ -21,6 +20,12 @@ export interface IJobManagementService {
|
||||
|
||||
getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult>;
|
||||
|
||||
getAlerts(connectionUri: string): Thenable<sqlops.AgentAlertsResult>;
|
||||
|
||||
getOperators(connectionUri: string): Thenable<sqlops.AgentOperatorsResult>;
|
||||
|
||||
getProxies(connectionUri: string): Thenable<sqlops.AgentProxiesResult>;
|
||||
|
||||
getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult>;
|
||||
|
||||
jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus>;
|
||||
|
||||
@@ -7,11 +7,8 @@
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
|
||||
|
||||
@@ -22,8 +19,7 @@ export class JobManagementService implements IJobManagementService {
|
||||
private _jobCacheObject : {[server: string]: JobCacheObject; } = {};
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -33,6 +29,25 @@ export class JobManagementService implements IJobManagementService {
|
||||
});
|
||||
}
|
||||
|
||||
public getAlerts(connectionUri: string): Thenable<sqlops.AgentAlertsResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getAlerts(connectionUri);
|
||||
});
|
||||
}
|
||||
|
||||
public getOperators(connectionUri: string): Thenable<sqlops.AgentOperatorsResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getOperators(connectionUri);
|
||||
});
|
||||
}
|
||||
|
||||
public getProxies(connectionUri: string): Thenable<sqlops.AgentProxiesResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getProxies(connectionUri);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getJobHistory(connectionUri, jobID);
|
||||
@@ -79,7 +94,8 @@ export class JobManagementService implements IJobManagementService {
|
||||
export class JobCacheObject {
|
||||
_serviceBrand: any;
|
||||
private _jobs: sqlops.AgentJobInfo[] = [];
|
||||
private _jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = {};
|
||||
private _jobHistories: { [jobID: string]: sqlops.AgentJobHistoryInfo[]; } = {};
|
||||
private _runCharts: { [jobID: string]: string[]; } = {};
|
||||
private _prevJobID: string;
|
||||
private _serverName: string;
|
||||
private _dataView: Slick.Data.DataView<any>;
|
||||
@@ -89,7 +105,7 @@ export class JobCacheObject {
|
||||
return this._jobs;
|
||||
}
|
||||
|
||||
public get jobHistories(): { [jobId: string]: sqlops.AgentJobHistoryInfo[] } {
|
||||
public get jobHistories(): { [jobID: string]: sqlops.AgentJobHistoryInfo[] } {
|
||||
return this._jobHistories;
|
||||
}
|
||||
|
||||
@@ -109,12 +125,16 @@ export class JobCacheObject {
|
||||
return this._dataView;
|
||||
}
|
||||
|
||||
public getRunChart(jobID: string): string[] {
|
||||
return this._runCharts[jobID];
|
||||
}
|
||||
|
||||
/* Setters */
|
||||
public set jobs(value: sqlops.AgentJobInfo[]) {
|
||||
this._jobs = value;
|
||||
}
|
||||
|
||||
public set jobHistories(value: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; }) {
|
||||
public set jobHistories(value: { [jobID: string]: sqlops.AgentJobHistoryInfo[]; }) {
|
||||
this._jobHistories = value;
|
||||
}
|
||||
|
||||
@@ -126,6 +146,10 @@ export class JobCacheObject {
|
||||
this._jobHistories[jobID] = value;
|
||||
}
|
||||
|
||||
public setRunChart(jobID: string, value: string[]) {
|
||||
this._runCharts[jobID] = value;
|
||||
}
|
||||
|
||||
public set serverName(value: string) {
|
||||
this._serverName = value;
|
||||
}
|
||||
|
||||
1
src/sql/parts/jobManagement/common/media/alert.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:#212121;}</style></defs><title>blocker</title><polygon class="cls-1" points="0.99 3.99 -0.01 3.99 -0.01 0.03 3.98 0.03 3.98 1.03 0.99 1.03 0.99 3.99"/><polygon class="cls-1" points="16.01 3.99 15.01 3.99 15.01 1.03 12.02 1.03 12.02 0.03 16.01 0.03 16.01 3.99"/><polygon class="cls-1" points="16.01 15.97 12.02 15.97 12.02 14.97 15.01 14.97 15.01 12.01 16.01 12.01 16.01 15.97"/><polygon class="cls-1" points="4 15.97 0.01 15.97 0.01 12.01 1.01 12.01 1.01 14.97 4 14.97 4 15.97"/><path class="cls-1" d="M8.41,3.18A4.82,4.82,0,1,0,13.23,8,4.83,4.83,0,0,0,8.41,3.18Zm0,.74A4.08,4.08,0,0,1,12.49,8a4,4,0,0,1-.85,2.47L5.69,5A4,4,0,0,1,8.41,3.93Zm0,8.15A4.08,4.08,0,0,1,4.34,8a4,4,0,0,1,.85-2.47L11.14,11A4,4,0,0,1,8.41,12.07Z"/></svg>
|
||||
|
After Width: | Height: | Size: 847 B |
@@ -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>blocker_inverse</title><polygon class="cls-1" points="0.99 3.99 -0.01 3.99 -0.01 0.03 3.98 0.03 3.98 1.03 0.99 1.03 0.99 3.99"/><polygon class="cls-1" points="16.01 3.99 15.01 3.99 15.01 1.03 12.02 1.03 12.02 0.03 16.01 0.03 16.01 3.99"/><polygon class="cls-1" points="16.01 15.97 12.02 15.97 12.02 14.97 15.01 14.97 15.01 12.01 16.01 12.01 16.01 15.97"/><polygon class="cls-1" points="4 15.97 0.01 15.97 0.01 12.01 1.01 12.01 1.01 14.97 4 14.97 4 15.97"/><path class="cls-1" d="M8.41,3.18A4.82,4.82,0,1,0,13.23,8,4.83,4.83,0,0,0,8.41,3.18Zm0,.74A4.08,4.08,0,0,1,12.49,8a4,4,0,0,1-.85,2.47L5.69,5A4,4,0,0,1,8.41,3.93Zm0,8.15A4.08,4.08,0,0,1,4.34,8a4,4,0,0,1,.85-2.47L11.14,11A4,4,0,0,1,8.41,12.07Z"/></svg>
|
||||
|
After Width: | Height: | Size: 852 B |
@@ -31,7 +31,7 @@ jobhistory-component {
|
||||
border-bottom: 3px solid #444444;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid {
|
||||
.jobview-grid {
|
||||
height: 94.7%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
@@ -49,6 +49,18 @@ jobhistory-component {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.vs-dark #agentViewDiv .slick-header-column {
|
||||
background: #333333 !important;
|
||||
}
|
||||
|
||||
#agentViewDiv .slick-header-column {
|
||||
background-color: transparent !important;
|
||||
background: white !important;
|
||||
border: 0px !important;
|
||||
font-weight: bold;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.vs-dark #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
|
||||
background:#333333;
|
||||
}
|
||||
@@ -97,7 +109,7 @@ jobhistory-component {
|
||||
|
||||
#jobsDiv .jobview-grid .slick-cell.l1.r1 .jobview-jobnametext {
|
||||
text-overflow: ellipsis;
|
||||
width: 200px;
|
||||
width: 250px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
@@ -107,18 +119,14 @@ jobhistory-component {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row {
|
||||
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row {
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
font-weight: 700;
|
||||
color: orangered;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.error-row {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row {
|
||||
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -182,6 +190,30 @@ jobhistory-component {
|
||||
background-image: url('./job_inverse.svg');
|
||||
}
|
||||
|
||||
.alertsview-icon {
|
||||
background-image: url('./alert.svg');
|
||||
}
|
||||
|
||||
.vs-dark .alertsview-icon {
|
||||
background-image: url('./alert_inverse.svg');
|
||||
}
|
||||
|
||||
.proxiesview-icon {
|
||||
background-image: url('./proxy.svg');
|
||||
}
|
||||
|
||||
.vs-dark .proxiesview-icon {
|
||||
background-image: url('./proxy_inverse.svg');
|
||||
}
|
||||
|
||||
.operatorsview-icon {
|
||||
background-image: url('./operator.svg');
|
||||
}
|
||||
|
||||
.vs-dark .operatorsview-icon {
|
||||
background-image: url('./operator_inverse.svg');
|
||||
}
|
||||
|
||||
agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.even > .slick-cell,
|
||||
agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.odd > .slick-cell {
|
||||
cursor: pointer;
|
||||
@@ -219,8 +251,8 @@ agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.od
|
||||
background: #444444 !important;
|
||||
}
|
||||
|
||||
table.jobprevruns div.bar1, table.jobprevruns div.bar2, table.jobprevruns div.bar3,
|
||||
table.jobprevruns div.bar4, table.jobprevruns div.bar5 {
|
||||
table.jobprevruns div.bar0, table.jobprevruns div.bar1, table.jobprevruns div.bar2,
|
||||
table.jobprevruns div.bar3, table.jobprevruns div.bar4, table.jobprevruns div.bar5 {
|
||||
padding-top: 3px;
|
||||
padding-left: 5px;
|
||||
width: 10px;
|
||||
@@ -232,10 +264,54 @@ table.jobprevruns div.bar4, table.jobprevruns div.bar5 {
|
||||
}
|
||||
|
||||
table.jobprevruns {
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
table.jobprevruns > tbody {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.jobs-view-toolbar{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
padding: 15px 10px 15px 30px;
|
||||
font-size: 16px;
|
||||
border-bottom: 3px solid #f4f4f4;
|
||||
}
|
||||
|
||||
.jobs-view-toolbar .vs-dark {
|
||||
border-bottom: 3px solid #444444;
|
||||
}
|
||||
|
||||
.jobs-view-toolbar > div{
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-start;
|
||||
margin-right: 25px;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.jobs-view-toolbar span{
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
#alertsDiv .jobalertsview-grid {
|
||||
height: 94.7%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#operatorsDiv .joboperatorsview-grid {
|
||||
height: 94.7%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#proxiesDiv .jobproxiesview-grid {
|
||||
height: 94.7%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
1
src/sql/parts/jobManagement/common/media/new.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:#231f20;}.cls-2{fill:#212121;}</style></defs><title>new_16x16</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path class="cls-2" d="M16,7.5v1H8.5V16h-1V8.5H0v-1H7.5V0h1V7.5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 336 B |
1
src/sql/parts/jobManagement/common/media/new_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>new_inverse_16x16</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path class="cls-1" d="M16,7.5v1H8.5V16h-1V8.5H0v-1H7.5V0h1V7.5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 320 B |
1
src/sql/parts/jobManagement/common/media/operator.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>security</title><path d="M6,8a4.88,4.88,0,0,0-1.33.18,5.11,5.11,0,0,0-1.2.5,5,5,0,0,0-1.79,1.79,5.11,5.11,0,0,0-.5,1.2A4.88,4.88,0,0,0,1,13H0a5.9,5.9,0,0,1,.28-1.79,6.12,6.12,0,0,1,2-2.94,5.33,5.33,0,0,1,1.58-.88,4.18,4.18,0,0,1-.79-.65,4,4,0,0,1-.59-.8A4.05,4.05,0,0,1,2.13,5a4,4,0,0,1,.18-2.57A4,4,0,0,1,4.44.31a4,4,0,0,1,3.12,0A4,4,0,0,1,9.69,2.44,4,4,0,0,1,9.87,5a4.05,4.05,0,0,1-.37.93,4,4,0,0,1-.59.8,4.18,4.18,0,0,1-.79.65,6.14,6.14,0,0,1,1,.5,5.73,5.73,0,0,1,.91.69l-.68.74a5,5,0,0,0-1.57-1A4.93,4.93,0,0,0,6,8ZM3,4a2.92,2.92,0,0,0,.23,1.17,3,3,0,0,0,1.6,1.6,3,3,0,0,0,2.33,0,3,3,0,0,0,1.6-1.6,3,3,0,0,0,0-2.33,3,3,0,0,0-1.6-1.6,3,3,0,0,0-2.33,0,3,3,0,0,0-1.6,1.6A2.92,2.92,0,0,0,3,4Zm12,8a2.45,2.45,0,0,1,0,1l1,.4-.38.93-1-.41a2.59,2.59,0,0,1-.67.67l.41,1-.93.38L13,15a2.45,2.45,0,0,1-1,0l-.4,1-.93-.38.41-1a2.59,2.59,0,0,1-.67-.67l-1,.41-.38-.93,1-.4a2.45,2.45,0,0,1,0-1l-1-.4.38-.93,1,.41a2.59,2.59,0,0,1,.67-.67l-.41-1,.93-.38.4,1a2.45,2.45,0,0,1,1,0l.4-1,.93.38-.41,1a2.59,2.59,0,0,1,.67.67l1-.41.38.93ZM12.5,14a1.47,1.47,0,0,0,.59-.12,1.49,1.49,0,0,0,.8-.8,1.52,1.52,0,0,0,0-1.17,1.49,1.49,0,0,0-.8-.8,1.52,1.52,0,0,0-1.17,0,1.49,1.49,0,0,0-.8.8,1.52,1.52,0,0,0,0,1.17,1.49,1.49,0,0,0,.8.8A1.47,1.47,0,0,0,12.5,14Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -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>security_inverse</title><path class="cls-1" d="M6,8a4.88,4.88,0,0,0-1.33.18,5.11,5.11,0,0,0-1.2.5,5,5,0,0,0-1.79,1.79,5.11,5.11,0,0,0-.5,1.2A4.88,4.88,0,0,0,1,13H0a5.9,5.9,0,0,1,.28-1.79,6.12,6.12,0,0,1,2-2.94,5.33,5.33,0,0,1,1.58-.88,4.18,4.18,0,0,1-.79-.65,4,4,0,0,1-.59-.8A4.05,4.05,0,0,1,2.13,5a4,4,0,0,1,.18-2.57A4,4,0,0,1,4.44.31a4,4,0,0,1,3.12,0A4,4,0,0,1,9.69,2.44,4,4,0,0,1,9.87,5a4.05,4.05,0,0,1-.37.93,4,4,0,0,1-.59.8,4.18,4.18,0,0,1-.79.65,6.14,6.14,0,0,1,1,.5,5.73,5.73,0,0,1,.91.69l-.68.74a5,5,0,0,0-1.57-1A4.93,4.93,0,0,0,6,8ZM3,4a2.92,2.92,0,0,0,.23,1.17,3,3,0,0,0,1.6,1.6,3,3,0,0,0,2.33,0,3,3,0,0,0,1.6-1.6,3,3,0,0,0,0-2.33,3,3,0,0,0-1.6-1.6,3,3,0,0,0-2.33,0,3,3,0,0,0-1.6,1.6A2.92,2.92,0,0,0,3,4Zm12,8a2.45,2.45,0,0,1,0,1l1,.4-.38.93-1-.41a2.59,2.59,0,0,1-.67.67l.41,1-.93.38L13,15a2.45,2.45,0,0,1-1,0l-.4,1-.93-.38.41-1a2.59,2.59,0,0,1-.67-.67l-1,.41-.38-.93,1-.4a2.45,2.45,0,0,1,0-1l-1-.4.38-.93,1,.41a2.59,2.59,0,0,1,.67-.67l-.41-1,.93-.38.4,1a2.45,2.45,0,0,1,1,0l.4-1,.93.38-.41,1a2.59,2.59,0,0,1,.67.67l1-.41.38.93ZM12.5,14a1.47,1.47,0,0,0,.59-.12,1.49,1.49,0,0,0,.8-.8,1.52,1.52,0,0,0,0-1.17,1.49,1.49,0,0,0-.8-.8,1.52,1.52,0,0,0-1.17,0,1.49,1.49,0,0,0-.8.8,1.52,1.52,0,0,0,0,1.17,1.49,1.49,0,0,0,.8.8A1.47,1.47,0,0,0,12.5,14Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/sql/parts/jobManagement/common/media/proxy.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:#101e23;}.cls-2{fill:#4bb8d1;}.cls-3{fill:#0c1011;}</style></defs><title>health</title><path class="cls-1" d="M12.58,1.51A6.36,6.36,0,0,0,8,3.9,6.32,6.32,0,0,0,3.41,1.51,3.81,3.81,0,0,0,0,5.35,5.7,5.7,0,0,0,.64,7.88h.72l0-.08A5.18,5.18,0,0,1,.64,5.39c.07-1.25.87-3.14,2.8-3.23h.12A5.81,5.81,0,0,1,7.73,4.63L8,5.06l.27-.43a5.72,5.72,0,0,1,4.28-2.47c1.93.09,2.73,2,2.8,3.23a5.15,5.15,0,0,1-.64,2.34l0,0a2.38,2.38,0,0,1-.34.68,19.45,19.45,0,0,1-6.57,6.06,11.11,11.11,0,0,1-1.25-.81c-.34-.25-.66-.52-1-.8h0a22.83,22.83,0,0,1-2.76-3H2a18.68,18.68,0,0,0,5.76,5.29h0l0,0h0c3.49-1.63,7-5.73,7.49-7.18V8A5.85,5.85,0,0,0,16,5.35,3.81,3.81,0,0,0,12.58,1.51Z"/><path class="cls-1" d="M1.41,8l-.1-.15h0Z"/><path class="cls-1" d="M7.79,15.22v0h0Z"/><path class="cls-1" d="M7.76,15.23h0v0Z"/><path class="cls-1" d="M14.72,7.73l0,0a.13.13,0,0,0,0,0Z"/><path class="cls-2" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/><path class="cls-3" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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;}.cls-2{fill:#101e23;}.cls-3{fill:#4bb8d1;}</style></defs><title>health_inverse</title><path class="cls-1" d="M12.58,1.51A6.36,6.36,0,0,0,8,3.9,6.32,6.32,0,0,0,3.41,1.51,3.81,3.81,0,0,0,0,5.35,5.7,5.7,0,0,0,.64,7.88h.72l0-.08A5.18,5.18,0,0,1,.64,5.39c.07-1.25.87-3.14,2.8-3.23h.12A5.81,5.81,0,0,1,7.73,4.63L8,5.06l.27-.43a5.72,5.72,0,0,1,4.28-2.47c1.93.09,2.73,2,2.8,3.23a5.15,5.15,0,0,1-.64,2.34l0,0a2.38,2.38,0,0,1-.34.68,19.45,19.45,0,0,1-6.57,6.06,11.11,11.11,0,0,1-1.25-.81c-.34-.25-.66-.52-1-.8h0a22.83,22.83,0,0,1-2.76-3H2a18.68,18.68,0,0,0,5.76,5.29h0l0,0h0c3.49-1.63,7-5.73,7.49-7.18V8A5.85,5.85,0,0,0,16,5.35,3.81,3.81,0,0,0,12.58,1.51Z"/><path class="cls-2" d="M1.41,8l-.1-.15h0Z"/><path class="cls-2" d="M7.79,15.22v0h0Z"/><path class="cls-2" d="M7.76,15.23h0v0Z"/><path class="cls-2" d="M14.72,7.73l0,0a.13.13,0,0,0,0,0Z"/><path class="cls-3" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/><path class="cls-1" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
17
src/sql/parts/jobManagement/views/alertsView.component.html
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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div class="job-heading-container">
|
||||
<h1 class="job-heading" *ngIf="_isCloud === false">Alerts</h1>
|
||||
<h1 class="job-heading" *ngIf="_isCloud === true">No Alerts Available</h1>
|
||||
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
|
||||
</div>
|
||||
<div class="jobs-view-toolbar">
|
||||
<div (click)="refreshJobs()" tabindex="0"><div class="small icon refresh"></div><span>{{RefreshText}}</span></div>
|
||||
<div (click)="openCreateJobDialog()" tabindex="0"><div class="small icon new"></div><span>{{NewAlertText}}</span></div>
|
||||
</div>
|
||||
|
||||
<div #jobalertsgrid class="jobview-grid"></div>
|
||||
160
src/sql/parts/jobManagement/views/alertsView.component.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/parts/grid/media/slickColorTheme';
|
||||
import 'vs/css!sql/parts/grid/media/flexbox';
|
||||
import 'vs/css!sql/parts/grid/media/styles';
|
||||
import 'vs/css!sql/parts/grid/media/slick.grid';
|
||||
import 'vs/css!sql/parts/grid/media/slickGrid';
|
||||
import 'vs/css!../common/media/jobs';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
import 'vs/css!sql/base/browser/ui/table/media/table';
|
||||
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IJobManagementService } from '../common/interfaces';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
export const VIEW_SELECTOR: string = 'jobalertsview-component';
|
||||
export const ROW_HEIGHT: number = 45;
|
||||
|
||||
@Component({
|
||||
selector: VIEW_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./alertsView.component.html')),
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => AlertsViewComponent) }],
|
||||
})
|
||||
export class AlertsViewComponent implements AfterContentChecked {
|
||||
|
||||
private columns: Array<Slick.Column<any>> = [
|
||||
{ name: nls.localize('jobAlertColumns.name', 'Name'), field: 'name', width: 200, id: 'name' },
|
||||
{ name: nls.localize('jobAlertColumns.lastOccurrenceDate', 'Last Occurrence'), field: 'lastOccurrenceDate', width: 200, id: 'lastOccurrenceDate' },
|
||||
{ name: nls.localize('jobAlertColumns.enabled', 'Enabled'), field: 'enabled', width: 200, id: 'enabled' },
|
||||
{ name: nls.localize('jobAlertColumns.databaseName', 'Database Name'), field: 'databaseName', width: 200, id: 'databaseName' },
|
||||
{ name: nls.localize('jobAlertColumns.categoryName', 'Category Name'), field: 'categoryName', width: 200, id: 'categoryName' },
|
||||
];
|
||||
|
||||
private options: Slick.GridOptions<any> = {
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: 45,
|
||||
enableCellNavigation: true,
|
||||
editable: false
|
||||
};
|
||||
|
||||
private dataView: any;
|
||||
|
||||
@ViewChild('jobalertsgrid') _gridEl: ElementRef;
|
||||
private isVisible: boolean = false;
|
||||
private isInitialized: boolean = false;
|
||||
private isRefreshing: boolean = false;
|
||||
private _table: Table<any>;
|
||||
public alerts: sqlops.AgentAlertInfo[];
|
||||
private _serverName: string;
|
||||
private _isCloud: boolean;
|
||||
private _showProgressWheel: boolean;
|
||||
|
||||
private NewAlertText: string = nls.localize('jobAlertToolbar-NewJob', "New Alert");
|
||||
private RefreshText: string = nls.localize('jobAlertToolbar-Refresh', "Refresh");
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(IThemeService) private _themeService: IThemeService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService
|
||||
) {
|
||||
this._isCloud = this._dashboardService.connectionManagementService.connectionInfo.serverInfo.isCloud;
|
||||
}
|
||||
|
||||
public layout() {
|
||||
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement)));
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
if (this.isVisible === false && this._gridEl.nativeElement.offsetParent !== null) {
|
||||
this.isVisible = true;
|
||||
if (!this.isInitialized) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(false);
|
||||
this.isInitialized = true;
|
||||
}
|
||||
} else if (this.isVisible === true && this._agentViewComponent.refresh === true) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(false);
|
||||
this.isRefreshing = true;
|
||||
this._agentViewComponent.refresh = false;
|
||||
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
onFirstVisible(cached?: boolean) {
|
||||
let self = this;
|
||||
let columns = this.columns.map((column) => {
|
||||
column.rerenderOnResize = true;
|
||||
return column;
|
||||
});
|
||||
let options = <Slick.GridOptions<any>>{
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: ROW_HEIGHT,
|
||||
enableCellNavigation: true,
|
||||
forceFitColumns: true
|
||||
};
|
||||
|
||||
this.dataView = new Slick.Data.DataView();
|
||||
|
||||
$(this._gridEl.nativeElement).empty();
|
||||
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
|
||||
this._table.grid.setData(this.dataView, true);
|
||||
|
||||
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._jobManagementService.getAlerts(ownerUri).then((result) => {
|
||||
if (result && result.alerts) {
|
||||
self.alerts = result.alerts;
|
||||
self.onAlertsAvailable(result.alerts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onAlertsAvailable(alerts: sqlops.AgentAlertInfo[]) {
|
||||
let items: any = alerts.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
lastOccurrenceDate: item.lastOccurrenceDate,
|
||||
enabled: item.isEnabled,
|
||||
databaseName: item.databaseName,
|
||||
categoryName: item.categoryName
|
||||
};
|
||||
});
|
||||
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(items);
|
||||
this.dataView.endUpdate();
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
|
||||
this._showProgressWheel = false;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
private openCreateJobDialog() {
|
||||
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._commandService.executeCommand('agent.openCreateAlertDialog', ownerUri);
|
||||
}
|
||||
|
||||
private refreshJobs() {
|
||||
this._agentViewComponent.refresh = true;
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,15 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
|
||||
import { IJobManagementService } from '../common/interfaces';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IConnectionManagementService } from '../../connection/common/connectionManagement';
|
||||
|
||||
export enum JobHistoryActions {
|
||||
export enum JobActions {
|
||||
Run = 'run',
|
||||
Stop = 'stop',
|
||||
NewStep = 'newStep'
|
||||
}
|
||||
|
||||
export class RunJobAction extends Action {
|
||||
public static ID = 'jobaction.runJob';
|
||||
public static LABEL = nls.localize('jobaction.run', "Run");
|
||||
@@ -30,7 +34,7 @@ export class RunJobAction extends Action {
|
||||
let jobName = context.agentJobInfo.name;
|
||||
let ownerUri = context.ownerUri;
|
||||
return new TPromise<boolean>((resolve, reject) => {
|
||||
this.jobManagementService.jobAction(ownerUri, jobName, JobHistoryActions.Run).then(result => {
|
||||
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Run).then(result => {
|
||||
if (result.success) {
|
||||
var startMsg = nls.localize('jobSuccessfullyStarted', ': The job was successfully started.');
|
||||
this.notificationService.notify({
|
||||
@@ -65,7 +69,7 @@ export class StopJobAction extends Action {
|
||||
let jobName = context.agentJobInfo.name;
|
||||
let ownerUri = context.ownerUri;
|
||||
return new TPromise<boolean>((resolve, reject) => {
|
||||
this.jobManagementService.jobAction(ownerUri, jobName, JobHistoryActions.Stop).then(result => {
|
||||
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Stop).then(result => {
|
||||
if (result.success) {
|
||||
var stopMsg = nls.localize('jobSuccessfullyStopped', ': The job was successfully stopped.');
|
||||
this.notificationService.notify({
|
||||
@@ -83,4 +87,30 @@ export class StopJobAction extends Action {
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class NewStepAction extends Action {
|
||||
public static ID = 'jobaction.newStep';
|
||||
public static LABEL = nls.localize('jobaction.newStep', "New Step");
|
||||
|
||||
constructor(
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
@IConnectionManagementService private _connectionService
|
||||
) {
|
||||
super(NewStepAction.ID, NewStepAction.LABEL, 'newStepIcon');
|
||||
}
|
||||
|
||||
public run(context: JobHistoryComponent): TPromise<boolean> {
|
||||
let ownerUri = context.ownerUri;
|
||||
let jobName = context.agentJobInfo.name;
|
||||
let server = context.serverName;
|
||||
let stepId = 0;
|
||||
if (context.agentJobHistoryInfo && context.agentJobHistoryInfo.steps) {
|
||||
stepId = context.agentJobHistoryInfo.steps.length + 1;
|
||||
}
|
||||
return new TPromise<boolean>((resolve, reject) => {
|
||||
resolve(this._commandService.executeCommand('agent.openNewStepDialog', ownerUri, jobName, server, stepId));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@
|
||||
<!-- Job History details -->
|
||||
<div class='history-details'>
|
||||
<!-- Previous run list -->
|
||||
<div style="min-width: 275px">
|
||||
<div class="prev-run-list-container" style="min-width: 275px; height: 75vh">
|
||||
<table *ngIf="_showPreviousRuns === true">
|
||||
<tr>
|
||||
<td class="date-column">
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
import 'vs/css!./jobHistory';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
import { OnInit, OnChanges, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy, Injectable } from '@angular/core';
|
||||
import { AgentJobHistoryInfo, AgentJobInfo } from 'sqlops';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { RunJobAction, StopJobAction } from 'sql/parts/jobManagement/views/jobHistoryActions';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { RunJobAction, StopJobAction, NewStepAction } from 'sql/parts/jobManagement/views/jobActions';
|
||||
import { JobCacheObject } from 'sql/parts/jobManagement/common/jobManagementService';
|
||||
import { AgentJobUtilities } from '../common/agentJobUtilities';
|
||||
import { IJobManagementService } from '../common/interfaces';
|
||||
@@ -46,9 +46,9 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
@ViewChild('table') private _tableContainer: ElementRef;
|
||||
@ViewChild('actionbarContainer') private _actionbarContainer: ElementRef;
|
||||
|
||||
@Input() public agentJobInfo: AgentJobInfo = undefined;
|
||||
@Input() public agentJobHistories: AgentJobHistoryInfo[] = undefined;
|
||||
public agentJobHistoryInfo: AgentJobHistoryInfo = undefined;
|
||||
@Input() public agentJobInfo: sqlops.AgentJobInfo = undefined;
|
||||
@Input() public agentJobHistories: sqlops.AgentJobHistoryInfo[] = undefined;
|
||||
public agentJobHistoryInfo: sqlops.AgentJobHistoryInfo = undefined;
|
||||
|
||||
private _isVisible: boolean = false;
|
||||
private _stepRows: JobStepsViewRow[] = [];
|
||||
@@ -56,8 +56,12 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
private _showPreviousRuns: boolean = undefined;
|
||||
private _runStatus: string = undefined;
|
||||
private _jobCacheObject: JobCacheObject;
|
||||
private _agentJobInfo: AgentJobInfo;
|
||||
private _agentJobInfo: sqlops.AgentJobInfo;
|
||||
private _noJobsAvailable: boolean = false;
|
||||
private _serverName: string;
|
||||
|
||||
private static readonly INITIAL_TREE_HEIGHT: number = 780;
|
||||
private static readonly HEADING_HEIGHT: number = 24;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
|
||||
@@ -76,14 +80,14 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
this._treeRenderer = new JobHistoryRenderer();
|
||||
this._treeFilter = new JobHistoryFilter();
|
||||
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
|
||||
let serverName = _dashboardService.connectionManagementService.connectionInfo.connectionProfile.serverName;
|
||||
let jobCache = jobCacheObjectMap[serverName];
|
||||
this._serverName = _dashboardService.connectionManagementService.connectionInfo.connectionProfile.serverName;
|
||||
let jobCache = jobCacheObjectMap[this._serverName];
|
||||
if (jobCache) {
|
||||
this._jobCacheObject = jobCache;
|
||||
} else {
|
||||
this._jobCacheObject = new JobCacheObject();
|
||||
this._jobCacheObject.serverName = serverName;
|
||||
this._jobManagementService.addToCache(serverName, this._jobCacheObject);
|
||||
this._jobCacheObject.serverName = this._serverName;
|
||||
this._jobManagementService.addToCache(this._serverName, this._jobCacheObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +128,17 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
renderer: this._treeRenderer
|
||||
}, {verticalScrollMode: ScrollbarVisibility.Visible});
|
||||
this._register(attachListStyler(this._tree, this.themeService));
|
||||
this._tree.layout(1024);
|
||||
this._tree.layout(JobHistoryComponent.INITIAL_TREE_HEIGHT);
|
||||
this._initActionBar();
|
||||
$(window).resize(() => {
|
||||
let historyDetails = $('.overview-container').get(0);
|
||||
let statusBar = $('.part.statusbar').get(0);
|
||||
if (historyDetails && statusBar) {
|
||||
let historyBottom = historyDetails.getBoundingClientRect().bottom;
|
||||
let statusTop = statusBar.getBoundingClientRect().top;
|
||||
this._tree.layout(statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
@@ -210,7 +223,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private buildHistoryTree(self: any, jobHistories: AgentJobHistoryInfo[]) {
|
||||
private buildHistoryTree(self: any, jobHistories: sqlops.AgentJobHistoryInfo[]) {
|
||||
self._treeController.jobHistories = jobHistories;
|
||||
self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, jobHistories);
|
||||
let jobHistoryRows = this._treeController.jobHistories.map(job => self.convertToJobHistoryRow(job));
|
||||
@@ -237,7 +250,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
this._agentViewComponent.showHistory = false;
|
||||
}
|
||||
|
||||
private convertToJobHistoryRow(historyInfo: AgentJobHistoryInfo): JobHistoryRow {
|
||||
private convertToJobHistoryRow(historyInfo: sqlops.AgentJobHistoryInfo): JobHistoryRow {
|
||||
let jobHistoryRow = new JobHistoryRow();
|
||||
jobHistoryRow.runDate = this.formatTime(historyInfo.runDate);
|
||||
jobHistoryRow.runStatus = AgentJobUtilities.convertToStatusString(historyInfo.runStatus);
|
||||
@@ -263,12 +276,14 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
private _initActionBar() {
|
||||
let runJobAction = this.instantiationService.createInstance(RunJobAction);
|
||||
let stopJobAction = this.instantiationService.createInstance(StopJobAction);
|
||||
let newStepAction = this.instantiationService.createInstance(NewStepAction);
|
||||
let taskbar = <HTMLElement>this._actionbarContainer.nativeElement;
|
||||
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
|
||||
this._actionBar.context = this;
|
||||
this._actionBar.setContent([
|
||||
{ action: runJobAction },
|
||||
{ action: stopJobAction }
|
||||
{ action: stopJobAction },
|
||||
{ action: newStepAction }
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -286,6 +301,10 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
return this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||
}
|
||||
|
||||
public get serverName(): string {
|
||||
return this._serverName;
|
||||
}
|
||||
|
||||
/** SETTERS */
|
||||
|
||||
public set showSteps(value: boolean) {
|
||||
|
||||
@@ -122,6 +122,15 @@ input#accordion:checked ~ .accordion-content {
|
||||
background-image: url('../common/media/stop.svg');
|
||||
}
|
||||
|
||||
.vs .action-label.icon.newStepIcon {
|
||||
background-image: url('../common/media/new.svg');
|
||||
}
|
||||
|
||||
.vs-dark .action-label.icon.newStepIcon,
|
||||
.hc-black .action-label.icon.newStepIcon {
|
||||
background-image: url('../common/media/new_inverse.svg');
|
||||
}
|
||||
|
||||
a.action-label.icon.runJobIcon.non-runnable {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
@@ -221,10 +230,6 @@ table.step-list tr.step-row td {
|
||||
min-height: 40px !important;
|
||||
}
|
||||
|
||||
jobhistory-component .history-details .step-table.prev-run-list .monaco-scrollable-element {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
jobhistory-component .jobhistory-heading-container {
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
@@ -9,5 +9,9 @@
|
||||
<h1 class="job-heading" *ngIf="_isCloud === true">No Jobs Available</h1>
|
||||
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
|
||||
</div>
|
||||
<div class="jobs-view-toolbar">
|
||||
<div (click)="refreshJobs()" tabindex="0"><div class="small icon refresh"></div><span>{{RefreshText}}</span></div>
|
||||
<div (click)="openCreateJobDialog()" tabindex="0"><div class="small icon new"></div><span>{{NewJobText}}</span></div>
|
||||
</div>
|
||||
|
||||
<div #jobsgrid class="jobview-grid"></div>
|
||||
@@ -27,7 +27,7 @@ import { IJobManagementService } from '../common/interfaces';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
|
||||
export const ROW_HEIGHT: number = 45;
|
||||
|
||||
@@ -44,16 +44,28 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
private _disposables = new Array<vscode.Disposable>();
|
||||
|
||||
private columns: Array<Slick.Column<any>> = [
|
||||
{ name: nls.localize('jobColumns.name','Name'), field: 'name', formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext), width: 200 , id: 'name' },
|
||||
{ name: nls.localize('jobColumns.lastRun','Last Run'), field: 'lastRun', width: 120, id: 'lastRun' },
|
||||
{ name: nls.localize('jobColumns.nextRun','Next Run'), field: 'nextRun', width: 120, id: 'nextRun' },
|
||||
{ name: nls.localize('jobColumns.enabled','Enabled'), field: 'enabled', width: 50, id: 'enabled' },
|
||||
{ name: nls.localize('jobColumns.status','Status'), field: 'currentExecutionStatus', width: 60, id: 'currentExecutionStatus' },
|
||||
{ name: nls.localize('jobColumns.category','Category'), field: 'category', width: 120, id: 'category' },
|
||||
{ name: nls.localize('jobColumns.runnable','Runnable'), field: 'runnable', width: 70, id: 'runnable' },
|
||||
{ name: nls.localize('jobColumns.schedule','Schedule'), field: 'hasSchedule', width: 60, id: 'hasSchedule' },
|
||||
{ name: nls.localize('jobColumns.lastRunOutcome', 'Last Run Outcome'), field: 'lastRunOutcome', width: 120, id: 'lastRunOutcome' },
|
||||
{ name: nls.localize('jobColumns.previousRuns', 'Previous Runs'), formatter: this.renderChartsPostHistory, field: 'previousRuns', width: 80, id: 'previousRuns'}
|
||||
{
|
||||
name: nls.localize('jobColumns.name', 'Name'),
|
||||
field: 'name',
|
||||
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
|
||||
width: 150,
|
||||
id: 'name'
|
||||
},
|
||||
{ name: nls.localize('jobColumns.lastRun', 'Last Run'), field: 'lastRun', width: 80, id: 'lastRun' },
|
||||
{ name: nls.localize('jobColumns.nextRun', 'Next Run'), field: 'nextRun', width: 80, id: 'nextRun' },
|
||||
{ name: nls.localize('jobColumns.enabled', 'Enabled'), field: 'enabled', width: 60, id: 'enabled' },
|
||||
{ name: nls.localize('jobColumns.status', 'Status'), field: 'currentExecutionStatus', width: 50, id: 'currentExecutionStatus' },
|
||||
{ name: nls.localize('jobColumns.category', 'Category'), field: 'category', width: 100, id: 'category' },
|
||||
{ name: nls.localize('jobColumns.runnable', 'Runnable'), field: 'runnable', width: 70, id: 'runnable' },
|
||||
{ name: nls.localize('jobColumns.schedule', 'Schedule'), field: 'hasSchedule', width: 60, id: 'hasSchedule' },
|
||||
{ name: nls.localize('jobColumns.lastRunOutcome', 'Last Run Outcome'), field: 'lastRunOutcome', width: 100, id: 'lastRunOutcome' },
|
||||
{
|
||||
name: nls.localize('jobColumns.previousRuns', 'Previous Runs'),
|
||||
formatter: (row, cell, value, columnDef, dataContext) => this.renderChartsPostHistory(row, cell, value, columnDef, dataContext),
|
||||
field: 'previousRuns',
|
||||
width: 100,
|
||||
id: 'previousRuns'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -79,19 +91,22 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
private _serverName: string;
|
||||
private _isCloud: boolean;
|
||||
private _showProgressWheel: boolean;
|
||||
private _tabHeight: number;
|
||||
private filterStylingMap: { [columnName: string]: [any] ;} = {};
|
||||
private filterStylingMap: { [columnName: string]: [any]; } = {};
|
||||
private filterStack = ['start'];
|
||||
private filterValueMap: { [columnName: string]: string[] ;} = {};
|
||||
private filterValueMap: { [columnName: string]: string[]; } = {};
|
||||
private sortingStylingMap: { [columnName: string]: any; } = {};
|
||||
|
||||
private NewJobText: string = nls.localize("jobsToolbar-NewJob", "New Job");
|
||||
private RefreshText: string = nls.localize("jobsToolbar-Refresh", "Refresh");
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(IThemeService) private _themeService: IThemeService
|
||||
@Inject(IThemeService) private _themeService: IThemeService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService
|
||||
) {
|
||||
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
|
||||
this._serverName = _dashboardService.connectionManagementService.connectionInfo.connectionProfile.serverName;
|
||||
@@ -130,13 +145,13 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
this.onFirstVisible(false);
|
||||
this.isRefreshing = true;
|
||||
this._agentViewComponent.refresh = false;
|
||||
} else if (this.isVisible === true && this._agentViewComponent.refresh === false) {
|
||||
} /*else if (this.isVisible === true && this._agentViewComponent.refresh === false) {
|
||||
if (!this.isRefreshing) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(true);
|
||||
}
|
||||
this.isRefreshing = false;
|
||||
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
|
||||
}*/ else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
@@ -241,9 +256,9 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}
|
||||
// apply the previous filter styling
|
||||
let currentItems = this.dataView.getFilteredItems();
|
||||
let styledItems = this.filterValueMap[this.filterStack[this.filterStack.length-1]][1];
|
||||
let styledItems = this.filterValueMap[this.filterStack[this.filterStack.length - 1]][1];
|
||||
if (styledItems === currentItems) {
|
||||
let lastColStyle = this.filterStylingMap[this.filterStack[this.filterStack.length-1]];
|
||||
let lastColStyle = this.filterStylingMap[this.filterStack[this.filterStack.length - 1]];
|
||||
for (let i = 0; i < lastColStyle.length; i++) {
|
||||
this._table.grid.setCellCssStyles(lastColStyle[i][0], lastColStyle[i][1]);
|
||||
}
|
||||
@@ -251,7 +266,7 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
// style it all over again
|
||||
let seenJobs = 0;
|
||||
for (let i = 0; i < currentItems.length; i++) {
|
||||
this._table.grid.removeCellCssStyles('error-row'+i.toString());
|
||||
this._table.grid.removeCellCssStyles('error-row' + i.toString());
|
||||
let item = this.dataView.getFilteredItems()[i];
|
||||
if (item.lastRunOutcome === 'Failed') {
|
||||
this.addToStyleHash(seenJobs, false, this.filterStylingMap, args.column.name);
|
||||
@@ -261,7 +276,7 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}
|
||||
// one expansion for the row and one for
|
||||
// the error detail
|
||||
seenJobs ++;
|
||||
seenJobs++;
|
||||
i++;
|
||||
}
|
||||
seenJobs++;
|
||||
@@ -277,7 +292,7 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
} else {
|
||||
let seenJobs = 0;
|
||||
for (let i = 0; i < this.jobs.length; i++) {
|
||||
this._table.grid.removeCellCssStyles('error-row'+i.toString());
|
||||
this._table.grid.removeCellCssStyles('error-row' + i.toString());
|
||||
let item = this.dataView.getItemByIdx(i);
|
||||
// current filter
|
||||
if (_.contains(filterValues, item[args.column.field])) {
|
||||
@@ -291,7 +306,7 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}
|
||||
// one expansion for the row and one for
|
||||
// the error detail
|
||||
seenJobs ++;
|
||||
seenJobs++;
|
||||
i++;
|
||||
}
|
||||
seenJobs++;
|
||||
@@ -331,21 +346,20 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
currentTarget.title = currentTarget.innerText;
|
||||
});
|
||||
this._showProgressWheel = false;
|
||||
this._cd.detectChanges();
|
||||
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
const self = this;
|
||||
this._tabHeight = $('agentview-component #jobsDiv .jobview-grid').get(0).clientHeight;
|
||||
$(window).resize(() => {
|
||||
let currentTab = $('agentview-component #jobsDiv .jobview-grid').get(0);
|
||||
if (currentTab) {
|
||||
let currentTabHeight = currentTab.clientHeight;
|
||||
if (currentTabHeight < self._tabHeight) {
|
||||
$('agentview-component #jobsDiv div.ui-widget').css('height', `${currentTabHeight - 22}px`);
|
||||
self._table.resizeCanvas();
|
||||
} else {
|
||||
$('agentview-component #jobsDiv div.ui-widget').css('height', `${currentTabHeight}px`);
|
||||
self._table.resizeCanvas();
|
||||
}
|
||||
self._tabHeight = currentTabHeight;
|
||||
let jobsViewToolbar = $('jobsview-component .jobs-view-toolbar').get(0);
|
||||
let statusBar = $('.part.statusbar').get(0);
|
||||
if (jobsViewToolbar && statusBar) {
|
||||
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom;
|
||||
let statusTop = statusBar.getBoundingClientRect().top;
|
||||
$('agentview-component #jobsDiv .jobview-grid').css('height', statusTop - toolbarBottom);
|
||||
self._table.resizeCanvas();
|
||||
}
|
||||
});
|
||||
this._table.grid.onColumnsResized.subscribe((e, data: any) => {
|
||||
@@ -357,49 +371,17 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
|
||||
// generate job charts again
|
||||
self.jobs.forEach(job => {
|
||||
let jobId = job.jobId;
|
||||
let jobHistories = self._jobCacheObject.getJobHistory(job.jobId);
|
||||
let previousRuns = jobHistories.slice(jobHistories.length-5, jobHistories.length);
|
||||
let previousRuns = jobHistories.slice(jobHistories.length - 5, jobHistories.length);
|
||||
self.createJobChart(job.jobId, previousRuns);
|
||||
});
|
||||
});
|
||||
$('#jobsDiv .jobview-grid .monaco-table .slick-viewport .grid-canvas .ui-widget-content.slick-row').hover((e) => {
|
||||
// highlight the error row as well if a failing job row is hovered
|
||||
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
|
||||
let target = $(e.currentTarget);
|
||||
let targetChildren = $(e.currentTarget.children);
|
||||
let siblings = target.nextAll().toArray();
|
||||
let top = parseInt(target.css('top'), 10);
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
let sibling = siblings[i];
|
||||
let siblingTop = parseInt($(sibling).css('top'), 10);
|
||||
if (siblingTop === top + ROW_HEIGHT) {
|
||||
$(sibling.children).addClass('hovered');
|
||||
sibling.onmouseenter = (e) => {
|
||||
targetChildren.addClass('hovered');
|
||||
};
|
||||
sibling.onmouseleave = (e) => {
|
||||
targetChildren.removeClass('hovered');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, (e) => {
|
||||
// switch back to original background
|
||||
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
|
||||
let target = $(e.currentTarget);
|
||||
let siblings = target.nextAll().toArray();
|
||||
let top = parseInt(target.css('top'), 10);
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
let sibling = siblings[i];
|
||||
let siblingTop = parseInt($(sibling).css('top'), 10);
|
||||
if (siblingTop === top + ROW_HEIGHT) {
|
||||
$(sibling.children).removeClass('hovered');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#jobsDiv .jobview-grid .monaco-table .slick-viewport .grid-canvas .ui-widget-content.slick-row').hover((e1) =>
|
||||
this.highlightErrorRows(e1), (e2) => this.hightlightNonErrorRows(e2));
|
||||
|
||||
this._table.grid.onScroll.subscribe((e) => {
|
||||
$('#jobsDiv .jobview-grid .monaco-table .slick-viewport .grid-canvas .ui-widget-content.slick-row').hover((e1) =>
|
||||
this.highlightErrorRows(e1), (e2) => this.hightlightNonErrorRows(e2));
|
||||
});
|
||||
// cache the dataview for future use
|
||||
this._jobCacheObject.dataView = this.dataView;
|
||||
@@ -407,6 +389,47 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
this.loadJobHistories();
|
||||
}
|
||||
|
||||
private highlightErrorRows(e) {
|
||||
// highlight the error row as well if a failing job row is hovered
|
||||
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
|
||||
let target = $(e.currentTarget);
|
||||
let targetChildren = $(e.currentTarget.children);
|
||||
let siblings = target.nextAll().toArray();
|
||||
let top = parseInt(target.css('top'), 10);
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
let sibling = siblings[i];
|
||||
let siblingTop = parseInt($(sibling).css('top'), 10);
|
||||
if (siblingTop === top + ROW_HEIGHT) {
|
||||
$(sibling.children).addClass('hovered');
|
||||
sibling.onmouseenter = (e) => {
|
||||
targetChildren.addClass('hovered');
|
||||
};
|
||||
sibling.onmouseleave = (e) => {
|
||||
targetChildren.removeClass('hovered');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private hightlightNonErrorRows(e) {
|
||||
// switch back to original background
|
||||
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
|
||||
let target = $(e.currentTarget);
|
||||
let siblings = target.nextAll().toArray();
|
||||
let top = parseInt(target.css('top'), 10);
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
let sibling = siblings[i];
|
||||
let siblingTop = parseInt($(sibling).css('top'), 10);
|
||||
if (siblingTop === top + ROW_HEIGHT) {
|
||||
$(sibling.children).removeClass('hovered');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setRowWithErrorClass(hash: { [index: number]: { [id: string]: string; } }, row: number, errorClass: string) {
|
||||
hash[row] = {
|
||||
'_detail_selector': errorClass,
|
||||
@@ -427,27 +450,27 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}
|
||||
|
||||
private addToStyleHash(row: number, start: boolean, map: any, columnName: string) {
|
||||
let hash : {
|
||||
let hash: {
|
||||
[index: number]: {
|
||||
[id: string]: string;
|
||||
}
|
||||
} = {};
|
||||
hash = this.setRowWithErrorClass(hash, row, 'job-with-error');
|
||||
hash = this.setRowWithErrorClass(hash, row+1, 'error-row');
|
||||
hash = this.setRowWithErrorClass(hash, row + 1, 'error-row');
|
||||
if (start) {
|
||||
if (map['start']) {
|
||||
map['start'].push(['error-row'+row.toString(), hash]);
|
||||
map['start'].push(['error-row' + row.toString(), hash]);
|
||||
} else {
|
||||
map['start'] = [['error-row'+row.toString(), hash]];
|
||||
map['start'] = [['error-row' + row.toString(), hash]];
|
||||
}
|
||||
} else {
|
||||
if (map[columnName]) {
|
||||
map[columnName].push(['error-row'+row.toString(), hash]);
|
||||
map[columnName].push(['error-row' + row.toString(), hash]);
|
||||
} else {
|
||||
map[columnName] = [['error-row'+row.toString(), hash]];
|
||||
map[columnName] = [['error-row' + row.toString(), hash]];
|
||||
}
|
||||
}
|
||||
this._table.grid.setCellCssStyles('error-row'+row.toString(), hash);
|
||||
this._table.grid.setCellCssStyles('error-row' + row.toString(), hash);
|
||||
}
|
||||
|
||||
private renderName(row, cell, value, columnDef, dataContext) {
|
||||
@@ -477,14 +500,28 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}
|
||||
|
||||
private renderChartsPostHistory(row, cell, value, columnDef, dataContext) {
|
||||
return `<table class="jobprevruns" id="${dataContext.id}">
|
||||
let runChart = this._jobCacheObject.getRunChart(dataContext.id);
|
||||
if (runChart && runChart.length > 0) {
|
||||
return `<table class="jobprevruns" id="${dataContext.id}">
|
||||
<tr>
|
||||
<td><div class="bar1"></div></td>
|
||||
<td><div class="bar2"></div></td>
|
||||
<td><div class="bar3"></div></td>
|
||||
<td><div class="bar4"></div></td>
|
||||
<td>${runChart[0] ? runChart[0] : '<div></div>' }</td>
|
||||
<td>${runChart[1] ? runChart[1] : '<div></div>' }</td>
|
||||
<td>${runChart[2] ? runChart[2] : '<div></div>' }</td>
|
||||
<td>${runChart[3] ? runChart[3] : '<div></div>' }</td>
|
||||
<td>${runChart[4] ? runChart[4] : '<div></div>' }</td>
|
||||
</tr>
|
||||
</table>`;
|
||||
</table>`;
|
||||
} else {
|
||||
return `<table class="jobprevruns" id="${dataContext.id}">
|
||||
<tr>
|
||||
<td><div class="bar0"></div></td>
|
||||
<td><div class="bar1"></div></td>
|
||||
<td><div class="bar2"></div></td>
|
||||
<td><div class="bar3"></div></td>
|
||||
<td><div class="bar4"></div></td>
|
||||
</tr>
|
||||
</table>`;
|
||||
}
|
||||
}
|
||||
|
||||
private expandJobRowDetails(rowIdx: number, message?: string): void {
|
||||
@@ -557,13 +594,13 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
self.jobHistories[job.jobId] = result.jobs;
|
||||
self._jobCacheObject.setJobHistory(job.jobId, result.jobs);
|
||||
let jobHistories = self._jobCacheObject.getJobHistory(job.jobId);
|
||||
let previousRuns = jobHistories.slice(jobHistories.length-5, jobHistories.length);
|
||||
let previousRuns = jobHistories.slice(jobHistories.length - 5, jobHistories.length);
|
||||
self.createJobChart(job.jobId, previousRuns);
|
||||
if (self._agentViewComponent.expanded.has(job.jobId)) {
|
||||
let lastJobHistory = jobHistories[result.jobs.length-1];
|
||||
let lastJobHistory = jobHistories[result.jobs.length - 1];
|
||||
let item = self.dataView.getItemById(job.jobId + '.error');
|
||||
let noStepsMessage = nls.localize('jobsView.noSteps', 'No Steps available for this job.');
|
||||
let errorMessage = lastJobHistory ? lastJobHistory.message: noStepsMessage;
|
||||
let errorMessage = lastJobHistory ? lastJobHistory.message : noStepsMessage;
|
||||
item['name'] = nls.localize('jobsView.error', 'Error: ') + errorMessage;
|
||||
self._agentViewComponent.setExpanded(job.jobId, item['name']);
|
||||
self.dataView.updateItem(job.jobId + '.error', item);
|
||||
@@ -575,8 +612,9 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
|
||||
private createJobChart(jobId: string, jobHistories: sqlops.AgentJobHistoryInfo[]): void {
|
||||
let chartHeights = this.getChartHeights(jobHistories);
|
||||
let runCharts = [];
|
||||
for (let i = 0; i < jobHistories.length; i++) {
|
||||
let runGraph = $(`table#${jobId}.jobprevruns > tbody > tr > td > div.bar${i+1}`);
|
||||
let runGraph = $(`table#${jobId}.jobprevruns > tbody > tr > td > div.bar${i}`);
|
||||
if (jobHistories && jobHistories.length > 0) {
|
||||
runGraph.css('height', chartHeights[i]);
|
||||
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
|
||||
@@ -585,6 +623,9 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
let currentTarget = e.currentTarget;
|
||||
currentTarget.title = jobHistories[i].runDuration;
|
||||
});
|
||||
if (runGraph.get(0)) {
|
||||
runCharts.push(runGraph.get(0).outerHTML);
|
||||
}
|
||||
} else {
|
||||
runGraph.css('height', '5px');
|
||||
runGraph.css('background', 'red');
|
||||
@@ -594,16 +635,19 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (runCharts.length > 0) {
|
||||
this._jobCacheObject.setRunChart(jobId, runCharts);
|
||||
}
|
||||
}
|
||||
|
||||
// chart height normalization logic
|
||||
private getChartHeights(jobHistories: sqlops.AgentJobHistoryInfo[]): string[] {
|
||||
if (!jobHistories || jobHistories.length === 0) {
|
||||
return ['5px','5px','5px','5px','5px'];
|
||||
return ['5px', '5px', '5px', '5px', '5px'];
|
||||
}
|
||||
let maxDuration: number = 0;
|
||||
jobHistories.forEach(history => {
|
||||
let historyDuration = AgentJobUtilities.convertDurationToSeconds(history.runDuration) ;
|
||||
let historyDuration = AgentJobUtilities.convertDurationToSeconds(history.runDuration);
|
||||
if (historyDuration > maxDuration) {
|
||||
maxDuration = historyDuration;
|
||||
}
|
||||
@@ -613,7 +657,7 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
let chartHeights = [];
|
||||
for (let i = 0; i < jobHistories.length; i++) {
|
||||
let duration = jobHistories[i].runDuration;
|
||||
let chartHeight = (maxBarHeight * AgentJobUtilities.convertDurationToSeconds(duration))/maxDuration;
|
||||
let chartHeight = (maxBarHeight * AgentJobUtilities.convertDurationToSeconds(duration)) / maxDuration;
|
||||
chartHeights.push(`${chartHeight}px`);
|
||||
}
|
||||
return chartHeights;
|
||||
@@ -625,14 +669,14 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}
|
||||
let expandedJobs = this._agentViewComponent.expanded;
|
||||
let expansions = 0;
|
||||
for (let i = 0; i < this.jobs.length; i++){
|
||||
for (let i = 0; i < this.jobs.length; i++) {
|
||||
let job = this.jobs[i];
|
||||
if (job.lastRunOutcome === 0 && !expandedJobs.get(job.jobId)) {
|
||||
this.expandJobRowDetails(i+expandedJobs.size);
|
||||
this.addToStyleHash(i+expandedJobs.size, start, this.filterStylingMap, undefined);
|
||||
this.expandJobRowDetails(i + expandedJobs.size);
|
||||
this.addToStyleHash(i + expandedJobs.size, start, this.filterStylingMap, undefined);
|
||||
this._agentViewComponent.setExpanded(job.jobId, 'Loading Error...');
|
||||
} else if (job.lastRunOutcome === 0 && expandedJobs.get(job.jobId)) {
|
||||
this.addToStyleHash(i+expansions, start, this.filterStylingMap, undefined);
|
||||
this.addToStyleHash(i + expansions, start, this.filterStylingMap, undefined);
|
||||
expansions++;
|
||||
}
|
||||
}
|
||||
@@ -648,7 +692,7 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
if (item._parent) {
|
||||
value = value && _.contains(filterValues, item._parent[col.field]);
|
||||
} else {
|
||||
value = value && _.contains(filterValues, item[col.field]);
|
||||
value = value && _.contains(filterValues, item[col.field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,8 +705,8 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
let jobItems = items.filter(x => x._parent === undefined);
|
||||
let errorItems = items.filter(x => x._parent !== undefined);
|
||||
this.sortingStylingMap[column] = items;
|
||||
switch(column) {
|
||||
case('Name'): {
|
||||
switch (column) {
|
||||
case ('Name'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => {
|
||||
@@ -670,13 +714,13 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}, isAscending);
|
||||
break;
|
||||
}
|
||||
case('Last Run'): {
|
||||
case ('Last Run'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => this.dateCompare(item1, item2, true), isAscending);
|
||||
break;
|
||||
}
|
||||
case ('Next Run') : {
|
||||
case ('Next Run'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => this.dateCompare(item1, item2, false), isAscending);
|
||||
@@ -737,7 +781,7 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
let item = jobItems[i];
|
||||
if (item._child) {
|
||||
let child = errorItems.find(error => error === item._child);
|
||||
jobItems.splice(i+1, 0, child);
|
||||
jobItems.splice(i + 1, 0, child);
|
||||
jobItemsLength++;
|
||||
}
|
||||
}
|
||||
@@ -751,12 +795,12 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < this.jobs.length; i++) {
|
||||
this._table.grid.removeCellCssStyles('error-row'+i.toString());
|
||||
this._table.grid.removeCellCssStyles('error-row' + i.toString());
|
||||
}
|
||||
}
|
||||
// add new style to the items back again
|
||||
items = this.filterStack.length > 1 ? this.dataView.getFilteredItems() : this.dataView.getItems();
|
||||
for (let i = 0; i < items.length; i ++) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
if (item.lastRunOutcome === 'Failed') {
|
||||
this.addToStyleHash(i, false, this.sortingStylingMap, column);
|
||||
@@ -784,4 +828,13 @@ export class JobsViewComponent implements AfterContentChecked {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private openCreateJobDialog() {
|
||||
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._commandService.executeCommand('agent.openCreateJobDialog', ownerUri);
|
||||
}
|
||||
|
||||
private refreshJobs() {
|
||||
this._agentViewComponent.refresh = true;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div class="job-heading-container">
|
||||
<h1 class="job-heading" *ngIf="_isCloud === false">Operators</h1>
|
||||
<h1 class="job-heading" *ngIf="_isCloud === true">No Operators Available</h1>
|
||||
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
|
||||
</div>
|
||||
<div class="jobs-view-toolbar">
|
||||
<div (click)="refreshJobs()" tabindex="0"><div class="small icon refresh"></div><span>{{RefreshText}}</span></div>
|
||||
<div (click)="openCreateOperatorDialog()" tabindex="0"><div class="small icon new"></div><span>{{NewOperatorText}}</span></div>
|
||||
</div>
|
||||
|
||||
<div #operatorsgrid class="joboperatorsview-grid"></div>
|
||||
157
src/sql/parts/jobManagement/views/operatorsView.component.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/parts/grid/media/slickColorTheme';
|
||||
import 'vs/css!sql/parts/grid/media/flexbox';
|
||||
import 'vs/css!sql/parts/grid/media/styles';
|
||||
import 'vs/css!sql/parts/grid/media/slick.grid';
|
||||
import 'vs/css!sql/parts/grid/media/slickGrid';
|
||||
import 'vs/css!../common/media/jobs';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
import 'vs/css!sql/base/browser/ui/table/media/table';
|
||||
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IJobManagementService } from '../common/interfaces';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
export const VIEW_SELECTOR: string = 'joboperatorsview-component';
|
||||
export const ROW_HEIGHT: number = 45;
|
||||
|
||||
@Component({
|
||||
selector: VIEW_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./operatorsView.component.html')),
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => OperatorsViewComponent) }],
|
||||
})
|
||||
|
||||
export class OperatorsViewComponent implements AfterContentChecked {
|
||||
|
||||
private columns: Array<Slick.Column<any>> = [
|
||||
{ name: nls.localize('jobOperatorsView.name', 'Name'), field: 'name', width: 200, id: 'name' },
|
||||
{ name: nls.localize('jobOperatorsView.emailAddress', 'Email Address'), field: 'emailAddress', width: 200, id: 'emailAddress' },
|
||||
{ name: nls.localize('jobOperatorsView.enabled', 'Enabled'), field: 'enabled', width: 200, id: 'enabled' },
|
||||
];
|
||||
|
||||
private options: Slick.GridOptions<any> = {
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: 45,
|
||||
enableCellNavigation: true,
|
||||
editable: false
|
||||
};
|
||||
|
||||
private dataView: any;
|
||||
|
||||
@ViewChild('operatorsgrid') _gridEl: ElementRef;
|
||||
private isVisible: boolean = false;
|
||||
private isInitialized: boolean = false;
|
||||
private isRefreshing: boolean = false;
|
||||
private _table: Table<any>;
|
||||
public operators: sqlops.AgentOperatorInfo[];
|
||||
private _serverName: string;
|
||||
private _isCloud: boolean;
|
||||
private _showProgressWheel: boolean;
|
||||
|
||||
private NewOperatorText: string = nls.localize('jobOperatorToolbar-NewItem', "New Operator");
|
||||
private RefreshText: string = nls.localize('jobOperatorToolbar-Refresh', "Refresh");
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(IThemeService) private _themeService: IThemeService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService
|
||||
) {
|
||||
this._isCloud = this._dashboardService.connectionManagementService.connectionInfo.serverInfo.isCloud;
|
||||
}
|
||||
|
||||
public layout() {
|
||||
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement)));
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
if (this.isVisible === false && this._gridEl.nativeElement.offsetParent !== null) {
|
||||
this.isVisible = true;
|
||||
if (!this.isInitialized) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(false);
|
||||
this.isInitialized = true;
|
||||
}
|
||||
} else if (this.isVisible === true && this._agentViewComponent.refresh === true) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(false);
|
||||
this.isRefreshing = true;
|
||||
this._agentViewComponent.refresh = false;
|
||||
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
onFirstVisible(cached?: boolean) {
|
||||
let self = this;
|
||||
let columns = this.columns.map((column) => {
|
||||
column.rerenderOnResize = true;
|
||||
return column;
|
||||
});
|
||||
let options = <Slick.GridOptions<any>>{
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: ROW_HEIGHT,
|
||||
enableCellNavigation: true,
|
||||
forceFitColumns: true
|
||||
};
|
||||
|
||||
this.dataView = new Slick.Data.DataView();
|
||||
|
||||
$(this._gridEl.nativeElement).empty();
|
||||
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
|
||||
this._table.grid.setData(this.dataView, true);
|
||||
|
||||
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._jobManagementService.getOperators(ownerUri).then((result) => {
|
||||
if (result && result.operators) {
|
||||
self.operators = result.operators;
|
||||
self.onOperatorsAvailable(result.operators);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onOperatorsAvailable(operators: sqlops.AgentOperatorInfo[]) {
|
||||
let items: any = operators.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
emailAddress: item.emailAddress,
|
||||
enabled: item.enabled
|
||||
};
|
||||
});
|
||||
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(items);
|
||||
this.dataView.endUpdate();
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
|
||||
this._showProgressWheel = false;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
private openCreateOperatorDialog() {
|
||||
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._commandService.executeCommand('agent.openCreateOperatorDialog', ownerUri);
|
||||
}
|
||||
|
||||
private refreshJobs() {
|
||||
this._agentViewComponent.refresh = true;
|
||||
}
|
||||
}
|
||||
17
src/sql/parts/jobManagement/views/proxiesView.component.html
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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div class="job-heading-container">
|
||||
<h1 class="job-heading" *ngIf="_isCloud === false">Proxies</h1>
|
||||
<h1 class="job-heading" *ngIf="_isCloud === true">No Proxies Available</h1>
|
||||
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
|
||||
</div>
|
||||
<div class="jobs-view-toolbar">
|
||||
<div (click)="refreshJobs()" tabindex="0"><div class="small icon refresh"></div><span>{{RefreshText}}</span></div>
|
||||
<div (click)="openCreateProxyDialog()" tabindex="0"><div class="small icon new"></div><span>{{NewProxyText}}</span></div>
|
||||
</div>
|
||||
|
||||
<div #proxiesgrid class="jobproxiesview-grid"></div>
|
||||
155
src/sql/parts/jobManagement/views/proxiesView.component.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/parts/grid/media/slickColorTheme';
|
||||
import 'vs/css!sql/parts/grid/media/flexbox';
|
||||
import 'vs/css!sql/parts/grid/media/styles';
|
||||
import 'vs/css!sql/parts/grid/media/slick.grid';
|
||||
import 'vs/css!sql/parts/grid/media/slickGrid';
|
||||
import 'vs/css!../common/media/jobs';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
import 'vs/css!sql/base/browser/ui/table/media/table';
|
||||
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IJobManagementService } from '../common/interfaces';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
export const VIEW_SELECTOR: string = 'jobproxiesview-component';
|
||||
export const ROW_HEIGHT: number = 45;
|
||||
|
||||
@Component({
|
||||
selector: VIEW_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./proxiesView.component.html')),
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => ProxiesViewComponent) }],
|
||||
})
|
||||
|
||||
export class ProxiesViewComponent implements AfterContentChecked {
|
||||
|
||||
private columns: Array<Slick.Column<any>> = [
|
||||
{ name: nls.localize('jobProxiesView.accountName', 'Account Name'), field: 'accountName', width: 200, id: 'accountName' },
|
||||
{ name: nls.localize('jobProxiesView.credentialName', 'Credential Name'), field: 'credentialName', width: 200, id: 'credentialName' },
|
||||
];
|
||||
|
||||
private options: Slick.GridOptions<any> = {
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: 45,
|
||||
enableCellNavigation: true,
|
||||
editable: false
|
||||
};
|
||||
|
||||
private dataView: any;
|
||||
|
||||
@ViewChild('proxiesgrid') _gridEl: ElementRef;
|
||||
private isVisible: boolean = false;
|
||||
private isInitialized: boolean = false;
|
||||
private isRefreshing: boolean = false;
|
||||
private _table: Table<any>;
|
||||
public proxies: sqlops.AgentProxyInfo[];
|
||||
private _serverName: string;
|
||||
private _isCloud: boolean;
|
||||
private _showProgressWheel: boolean;
|
||||
|
||||
private NewProxyText: string = nls.localize('jobProxyToolbar-NewItem', "New Proxy");
|
||||
private RefreshText: string = nls.localize('jobProxyToolbar-Refresh', "Refresh");
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(IThemeService) private _themeService: IThemeService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService
|
||||
) {
|
||||
this._isCloud = this._dashboardService.connectionManagementService.connectionInfo.serverInfo.isCloud;
|
||||
}
|
||||
|
||||
public layout() {
|
||||
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement)));
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
if (this.isVisible === false && this._gridEl.nativeElement.offsetParent !== null) {
|
||||
this.isVisible = true;
|
||||
if (!this.isInitialized) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(false);
|
||||
this.isInitialized = true;
|
||||
}
|
||||
} else if (this.isVisible === true && this._agentViewComponent.refresh === true) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible(false);
|
||||
this.isRefreshing = true;
|
||||
this._agentViewComponent.refresh = false;
|
||||
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
onFirstVisible(cached?: boolean) {
|
||||
let self = this;
|
||||
let columns = this.columns.map((column) => {
|
||||
column.rerenderOnResize = true;
|
||||
return column;
|
||||
});
|
||||
let options = <Slick.GridOptions<any>>{
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: ROW_HEIGHT,
|
||||
enableCellNavigation: true,
|
||||
forceFitColumns: true
|
||||
};
|
||||
|
||||
this.dataView = new Slick.Data.DataView();
|
||||
|
||||
$(this._gridEl.nativeElement).empty();
|
||||
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
|
||||
this._table.grid.setData(this.dataView, true);
|
||||
|
||||
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._jobManagementService.getProxies(ownerUri).then((result) => {
|
||||
if (result && result.proxies) {
|
||||
self.proxies = result.proxies;
|
||||
self.onProxiesAvailable(result.proxies);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onProxiesAvailable(proxies: sqlops.AgentProxyInfo[]) {
|
||||
let items: any = proxies.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
accountName: item.accountName,
|
||||
credentialName: item.credentialName
|
||||
};
|
||||
});
|
||||
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(items);
|
||||
this.dataView.endUpdate();
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
|
||||
this._showProgressWheel = false;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
private openCreateProxyDialog() {
|
||||
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._commandService.executeCommand('agent.openCreateProxyDialog', ownerUri);
|
||||
}
|
||||
|
||||
private refreshJobs() {
|
||||
this._agentViewComponent.refresh = true;
|
||||
}
|
||||
}
|
||||
@@ -207,7 +207,9 @@ export abstract class ContainerBase<T> extends ComponentBase {
|
||||
) {
|
||||
super(_changeRef);
|
||||
this.items = [];
|
||||
this._validations.push(() => this.items.every(item => this.modelStore.getComponent(item.descriptor.id).valid));
|
||||
this._validations.push(() => this.items.every(item => {
|
||||
return this.modelStore.getComponent(item.descriptor.id).valid;
|
||||
}));
|
||||
}
|
||||
|
||||
/// IComponent container-related implementation
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
[role="gridcell"]:focus,
|
||||
[role="gridcell"] *:focus,
|
||||
[role="grid"] [tabindex="0"]:focus {
|
||||
.declarative-table [role="gridcell"]:focus,
|
||||
.declarative-table [role="gridcell"] *:focus,
|
||||
.declarative-table [role="grid"] [tabindex="0"]:focus {
|
||||
outline: #005a9c;
|
||||
outline-style: dotted;
|
||||
outline-width: 3px;
|
||||
|
||||
@@ -18,9 +18,8 @@ import { IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox
|
||||
import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TextAreaInput } from 'vs/editor/browser/controller/textAreaInput';
|
||||
import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
@Component({
|
||||
selector: 'modelview-inputBox',
|
||||
@@ -70,13 +69,13 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
if (this._inputContainer) {
|
||||
this._input = new InputBox(this._inputContainer.nativeElement, this.contextViewService, inputOptions);
|
||||
this.registerInput(this._input, () => !this.multiline);
|
||||
|
||||
}
|
||||
if (this._textareaContainer) {
|
||||
let textAreaInputOptions = Object.assign({}, inputOptions, { flexibleHeight: true, type: 'textarea' });
|
||||
this._textAreaInput = new InputBox(this._textareaContainer.nativeElement, this.contextViewService, textAreaInputOptions);
|
||||
this.registerInput(this._textAreaInput, () => this.multiline);
|
||||
}
|
||||
this.inputElement.hideErrors = true;
|
||||
}
|
||||
|
||||
private get inputElement(): InputBox {
|
||||
@@ -88,10 +87,17 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
this._validations.push(() => !input.inputElement.validationMessage);
|
||||
|
||||
this._register(input);
|
||||
this._register(attachInputBoxStyler(input, this.themeService));
|
||||
this._register(input.onDidChange(e => {
|
||||
this._register(attachInputBoxStyler(input, this.themeService, {
|
||||
inputValidationInfoBackground: inputBackground,
|
||||
inputValidationInfoBorder: inputBorder,
|
||||
}));
|
||||
this._register(input.onDidChange(async e => {
|
||||
if (checkOption()) {
|
||||
this.value = input.value;
|
||||
await this.validate();
|
||||
if (input.hideErrors) {
|
||||
input.hideErrors = false;
|
||||
}
|
||||
this._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.onDidChange,
|
||||
args: e
|
||||
|
||||
@@ -94,8 +94,8 @@ export class ModelComponentWrapper extends AngularDisposable implements OnInit {
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
if (this._componentInstance && this._componentInstance.layout) {
|
||||
this._componentInstance.layout();
|
||||
if (this.componentInstance && this.componentInstance.layout) {
|
||||
this.componentInstance.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,12 @@ export class ModelComponentWrapper extends AngularDisposable implements OnInit {
|
||||
};
|
||||
}
|
||||
|
||||
private get componentInstance(): IComponent {
|
||||
if (!this._componentInstance) {
|
||||
this.loadComponent();
|
||||
}
|
||||
return this._componentInstance;
|
||||
}
|
||||
|
||||
private loadComponent(): void {
|
||||
if (!this.descriptor || !this.descriptor.type) {
|
||||
|
||||
@@ -73,7 +73,7 @@ export class ModelViewInput extends EditorInput {
|
||||
|
||||
private createDialogPane(): void {
|
||||
this._dialogPaneContainer = DOM.$('div.model-view-container');
|
||||
this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService);
|
||||
this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService, false);
|
||||
this._dialogPane.createBody(this._dialogPaneContainer);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import * as sqlops from 'sqlops';
|
||||
selector: 'modelview-content',
|
||||
template: `
|
||||
<div *ngIf="rootDescriptor" style="width: 100%; height: 100%;">
|
||||
<model-component-wrapper [descriptor]="rootDescriptor" [modelStore]="modelStore">
|
||||
<model-component-wrapper style="display: block; height: 100%" [descriptor]="rootDescriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -12,7 +12,7 @@ import nls = require('vs/nls');
|
||||
import * as sqlops from 'sqlops';
|
||||
import { IModelStore, IComponentDescriptor, IComponent, IComponentEventArgs } from './interfaces';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
import { IModelView, IModelViewEventArgs } from 'sql/services/model/modelViewService';
|
||||
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { ModelStore } from 'sql/parts/modelComponents/modelStore';
|
||||
@@ -38,7 +38,7 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
abstract id: string;
|
||||
abstract connection: sqlops.connection.Connection;
|
||||
abstract serverInfo: sqlops.ServerInfo;
|
||||
private _onEventEmitter = new Emitter<any>();
|
||||
private _onEventEmitter = new Emitter<IModelViewEventArgs>();
|
||||
|
||||
initializeModel(rootComponent: IComponentShape, validationCallback: (componentId: string) => Thenable<boolean>): void {
|
||||
let descriptor = this.defineComponent(rootComponent);
|
||||
@@ -106,13 +106,16 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
registerEvent(componentId: string) {
|
||||
this.queueAction(componentId, (component) => {
|
||||
this._register(component.registerEventHandler(e => {
|
||||
e.componentId = componentId;
|
||||
this._onEventEmitter.fire(e);
|
||||
let modelViewEvent: IModelViewEventArgs = Object.assign({
|
||||
componentId: componentId,
|
||||
isRootComponent: componentId === this.rootDescriptor.id
|
||||
}, e);
|
||||
this._onEventEmitter.fire(modelViewEvent);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
public get onEvent(): Event<IComponentEventArgs> {
|
||||
public get onEvent(): Event<IModelViewEventArgs> {
|
||||
return this._onEventEmitter.event;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,11 +96,11 @@ export class TreeNode {
|
||||
return undefined;
|
||||
}
|
||||
var currentNode: TreeNode = this;
|
||||
while (currentNode.nodeTypeId !== NodeType.Database && currentNode.nodeTypeId !== NodeType.Server) {
|
||||
while (currentNode.nodeTypeId !== NodeType.Database && currentNode.nodeTypeId !== NodeType.Server && currentNode.parent) {
|
||||
currentNode = currentNode.parent;
|
||||
}
|
||||
|
||||
if (currentNode.nodeTypeId === NodeType.Database) {
|
||||
if (currentNode && currentNode.nodeTypeId === NodeType.Database) {
|
||||
return currentNode.metadata ? currentNode.metadata.name : null;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -181,7 +181,7 @@ export class TreeUpdateUtils {
|
||||
connectedConnection.options['groupId'] = connection.groupId;
|
||||
connectedConnection.options['databaseDisplayName'] = connection.databaseName;
|
||||
|
||||
var rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
|
||||
let rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
|
||||
if (!rootNode) {
|
||||
objectExplorerService.updateObjectExplorerNodes(connectedConnection).then(() => {
|
||||
rootNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
|
||||
@@ -207,7 +207,7 @@ export class TreeUpdateUtils {
|
||||
if (connection.isDisconnecting) {
|
||||
resolve([]);
|
||||
} else {
|
||||
var rootNode = objectExplorerService.getObjectExplorerNode(connection);
|
||||
let rootNode = objectExplorerService.getObjectExplorerNode(connection);
|
||||
if (rootNode) {
|
||||
objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode).then(() => {
|
||||
resolve(rootNode.children);
|
||||
@@ -226,7 +226,7 @@ export class TreeUpdateUtils {
|
||||
if (objectExplorerNode && objectExplorerNode.parent) {
|
||||
// if object explorer node's parent is root, return connection profile
|
||||
if (!objectExplorerNode.parent.parent) {
|
||||
var connectionId = objectExplorerNode.getConnectionProfile().id;
|
||||
let connectionId = objectExplorerNode.getConnectionProfile().id;
|
||||
|
||||
// get connection profile from connection profile groups
|
||||
let root = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||
@@ -268,8 +268,8 @@ export class TreeUpdateUtils {
|
||||
* Get connection profile with the current database
|
||||
*/
|
||||
public static getConnectionProfile(treeNode: TreeNode): ConnectionProfile {
|
||||
var connectionProfile = treeNode.getConnectionProfile();
|
||||
var databaseName = treeNode.getDatabaseName();
|
||||
let connectionProfile = treeNode.getConnectionProfile();
|
||||
let databaseName = treeNode.getDatabaseName();
|
||||
if (databaseName !== undefined && connectionProfile.databaseName !== databaseName) {
|
||||
connectionProfile = connectionProfile.cloneWithDatabase(databaseName);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ import { IObjectExplorerService } from '../../objectExplorer/common/objectExplor
|
||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||
import { IProfilerService} from '../service/interfaces';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeyCode, KeyMod } from 'vs/editor/editor.api';
|
||||
import { ProfilerEditor } from '../editor/profilerEditor';
|
||||
|
||||
// Contribute Global Actions
|
||||
const category = nls.localize('profilerCategory', "Profiler");
|
||||
@@ -46,3 +50,42 @@ CommandsRegistry.registerCommand({
|
||||
return editorService.openEditor(profilerInput, { pinned: true }, false).then(() => TPromise.as(true));
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'profiler.newProfiler',
|
||||
weight: KeybindingsRegistry.WEIGHT.builtinExtension(),
|
||||
when: undefined,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P },
|
||||
handler: CommandsRegistry.getCommand('profiler.newProfiler').handler
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'profiler.start',
|
||||
handler: (accessor: ServicesAccessor) => {
|
||||
let profilerService: IProfilerService = accessor.get(IProfilerService);
|
||||
let editorService: IWorkbenchEditorService = accessor.get(IWorkbenchEditorService);
|
||||
|
||||
let activeEditor = editorService.getActiveEditor();
|
||||
if (activeEditor instanceof ProfilerEditor) {
|
||||
let profilerInput = activeEditor.input;
|
||||
return profilerService.startSession(profilerInput.id);
|
||||
}
|
||||
return TPromise.as(false);
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'profiler.stop',
|
||||
handler: (accessor: ServicesAccessor) => {
|
||||
let profilerService: IProfilerService = accessor.get(IProfilerService);
|
||||
let editorService: IWorkbenchEditorService = accessor.get(IWorkbenchEditorService);
|
||||
|
||||
let activeEditor = editorService.getActiveEditor();
|
||||
if (activeEditor instanceof ProfilerEditor) {
|
||||
let profilerInput = activeEditor.input;
|
||||
return profilerService.stopSession(profilerInput.id);
|
||||
}
|
||||
return TPromise.as(false);
|
||||
}
|
||||
});
|
||||
@@ -79,10 +79,7 @@ export class ProfilerStart extends Action {
|
||||
public run(input: ProfilerInput): TPromise<boolean> {
|
||||
this.enabled = false;
|
||||
input.data.clear();
|
||||
return TPromise.wrap(this._profilerService.startSession(input.id).then(() => {
|
||||
input.state.change({ isRunning: true, isStopped: false, isPaused: false });
|
||||
return true;
|
||||
}));
|
||||
return TPromise.wrap(this._profilerService.startSession(input.id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,10 +128,7 @@ export class ProfilerStop extends Action {
|
||||
|
||||
public run(input: ProfilerInput): TPromise<boolean> {
|
||||
this.enabled = false;
|
||||
return TPromise.wrap(this._profilerService.stopSession(input.id).then(() => {
|
||||
input.state.change({ isStopped: true, isPaused: false, isRunning: false });
|
||||
return true;
|
||||
}));
|
||||
return TPromise.wrap(this._profilerService.stopSession(input.id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,10 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
|
||||
});
|
||||
}
|
||||
|
||||
public onSessionStateChanged(state: ProfilerState) {
|
||||
this.state.change(state);
|
||||
}
|
||||
|
||||
public onMoreRows(eventMessage: sqlops.ProfilerSessionEvents) {
|
||||
if (eventMessage.eventsLost){
|
||||
this._notificationService.warn(nls.localize("profiler.eventsLost", "The XEvent Profiler session for {0} has lost events.", this._connection.serverName));
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { INewProfilerState } from '../editor/profilerState';
|
||||
|
||||
const PROFILER_SERVICE_ID = 'profilerService';
|
||||
export const IProfilerService = createDecorator<IProfilerService>(PROFILER_SERVICE_ID);
|
||||
@@ -29,6 +30,10 @@ export interface IProfilerSession {
|
||||
* Called by the service when the session is closed unexpectedly
|
||||
*/
|
||||
onSessionStopped(events: sqlops.ProfilerSessionStoppedParams);
|
||||
/**
|
||||
* Called by the service when the session state is changed
|
||||
*/
|
||||
onSessionStateChanged(newState: INewProfilerState);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -96,7 +96,10 @@ export class ProfilerService implements IProfilerService {
|
||||
}
|
||||
|
||||
public startSession(id: ProfilerSessionID): Thenable<boolean> {
|
||||
return this._runAction(id, provider => provider.startSession(this._idMap.get(id)));
|
||||
return this._runAction(id, provider => provider.startSession(this._idMap.get(id))).then(() => {
|
||||
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false });
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public pauseSession(id: ProfilerSessionID): Thenable<boolean> {
|
||||
@@ -104,7 +107,10 @@ export class ProfilerService implements IProfilerService {
|
||||
}
|
||||
|
||||
public stopSession(id: ProfilerSessionID): Thenable<boolean> {
|
||||
return this._runAction(id, provider => provider.stopSession(this._idMap.get(id)));
|
||||
return this._runAction(id, provider => provider.stopSession(this._idMap.get(id))).then(() => {
|
||||
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isStopped: true, isPaused: false, isRunning: false });
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private _runAction<T>(id: ProfilerSessionID, action: (handler: sqlops.ProfilerProvider) => Thenable<T>): Thenable<T> {
|
||||
|
||||
@@ -306,6 +306,11 @@ let registryProperties = {
|
||||
'default': Constants.tabColorModeOff,
|
||||
'description': localize('tabColorMode', "Controls how to color tabs based on the server group of their active connection")
|
||||
},
|
||||
'sql.showConnectionInfoInTitle': {
|
||||
'type': 'boolean',
|
||||
'description': localize('showConnectionInfoInTitle', "Controls whether to show the connection info for a tab in the title."),
|
||||
'default': false
|
||||
},
|
||||
'mssql.intelliSense.enableIntelliSense': {
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
|
||||
@@ -4,17 +4,35 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput, EditorModel, ConfirmResult, EncodingMode, IEncodingSupport } from 'vs/workbench/common/editor';
|
||||
import { IConnectionManagementService, IConnectableInput, INewConnectionParams, RunQueryOnConnectionMode } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ISelectionData, ExecutionPlanOptions } from 'sqlops';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { EditorInput, EditorModel, ConfirmResult, EncodingMode, IEncodingSupport } from 'vs/workbench/common/editor';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
import { IConnectionManagementService, IConnectableInput, INewConnectionParams, RunQueryOnConnectionMode } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
|
||||
|
||||
import { ISelectionData, ExecutionPlanOptions } from 'sqlops';
|
||||
|
||||
const MAX_SIZE = 13;
|
||||
|
||||
function trimTitle(title: string): string {
|
||||
const length = title.length;
|
||||
const diff = length - MAX_SIZE;
|
||||
|
||||
if (Math.sign(diff) <= 0) {
|
||||
return title;
|
||||
} else {
|
||||
const start = (length / 2) - (diff / 2);
|
||||
return title.slice(0, start) + '...' + title.slice(start + diff, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Input for the QueryEditor. This input is simply a wrapper around a QueryResultsInput for the QueryResultsEditor
|
||||
* and a UntitledEditorInput for the SQL File Editor.
|
||||
@@ -39,17 +57,16 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
|
||||
private _currentEventCallbacks: IDisposable[];
|
||||
|
||||
constructor(
|
||||
private _name: string,
|
||||
private _description: string,
|
||||
private _sql: UntitledEditorInput,
|
||||
private _results: QueryResultsInput,
|
||||
private _connectionProviderName: string,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IQueryEditorService private _queryEditorService: IQueryEditorService
|
||||
@IQueryEditorService private _queryEditorService: IQueryEditorService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
let self = this;
|
||||
this._updateTaskbar = new Emitter<void>();
|
||||
this._showQueryResultsEditor = new Emitter<void>();
|
||||
this._updateSelection = new Emitter<ISelectionData>();
|
||||
@@ -66,36 +83,44 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
|
||||
// Register callbacks for the Actions
|
||||
this._toDispose.push(
|
||||
this._queryModelService.onRunQueryStart(uri => {
|
||||
if (self.uri === uri) {
|
||||
self.onRunQuery();
|
||||
if (this.uri === uri) {
|
||||
this.onRunQuery();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._toDispose.push(
|
||||
this._queryModelService.onRunQueryComplete(uri => {
|
||||
if (self.uri === uri) {
|
||||
self.onQueryComplete();
|
||||
if (this.uri === uri) {
|
||||
this.onQueryComplete();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (this._connectionManagementService) {
|
||||
this._toDispose.push(self._connectionManagementService.onDisconnect(result => {
|
||||
if (result.connectionUri === self.uri) {
|
||||
self.onDisconnect();
|
||||
this._toDispose.push(this._connectionManagementService.onDisconnect(result => {
|
||||
if (result.connectionUri === this.uri) {
|
||||
this.onDisconnect();
|
||||
}
|
||||
}));
|
||||
if (self.uri) {
|
||||
if (this.uri) {
|
||||
if (this._connectionProviderName) {
|
||||
this._connectionManagementService.doChangeLanguageFlavor(self.uri, 'sql', this._connectionProviderName);
|
||||
this._connectionManagementService.doChangeLanguageFlavor(this.uri, 'sql', this._connectionProviderName);
|
||||
} else {
|
||||
this._connectionManagementService.ensureDefaultLanguageFlavor(self.uri);
|
||||
this._connectionManagementService.ensureDefaultLanguageFlavor(this.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._configurationService) {
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectedKeys.includes('sql.showConnectionInfoInTitle')) {
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.onDisconnect();
|
||||
this.onQueryComplete();
|
||||
}
|
||||
@@ -136,11 +161,31 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
|
||||
public resolve(refresh?: boolean): TPromise<EditorModel> { return this._sql.resolve(); }
|
||||
public save(): TPromise<boolean> { return this._sql.save(); }
|
||||
public isDirty(): boolean { return this._sql.isDirty(); }
|
||||
public confirmSave(): TPromise<ConfirmResult> { return this._sql.confirmSave(); }
|
||||
public confirmSave(): TPromise<ConfirmResult> { return this._sql.confirmSave(); }
|
||||
public getResource(): URI { return this._sql.getResource(); }
|
||||
public getEncoding(): string { return this._sql.getEncoding(); }
|
||||
public suggestFileName(): string { return this._sql.suggestFileName(); }
|
||||
public getName(): string { return this._sql.getName(); }
|
||||
|
||||
public getName(): string {
|
||||
if (this._configurationService.getValue('sql.showConnectionInfoInTitle')) {
|
||||
let profile = this._connectionManagementService.getConnectionProfile(this.uri);
|
||||
let title = '';
|
||||
if (profile) {
|
||||
if (profile.userName) {
|
||||
title = `${profile.serverName}.${profile.databaseName} (${profile.userName})`;
|
||||
} else {
|
||||
title = `${profile.serverName}.${profile.databaseName} (${profile.authenticationType})`;
|
||||
}
|
||||
} else {
|
||||
title = localize('disconnected', 'disconnected');
|
||||
}
|
||||
|
||||
return this._sql.getName() + ` - ${trimTitle(title)}`;
|
||||
} else {
|
||||
return this._sql.getName();
|
||||
}
|
||||
}
|
||||
|
||||
public get hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
|
||||
|
||||
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface IQueryManagementService {
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
|
||||
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>;
|
||||
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Thenable<void>;
|
||||
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
|
||||
@@ -63,6 +64,7 @@ export interface IQueryRequestHandler {
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
|
||||
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>;
|
||||
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Thenable<void>;
|
||||
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
|
||||
@@ -197,6 +199,11 @@ export class QueryManagementService implements IQueryManagementService {
|
||||
return runner.runQueryAndReturn(ownerUri, queryString);
|
||||
});
|
||||
}
|
||||
public parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.parseSyntax(ownerUri, query);
|
||||
});
|
||||
}
|
||||
public getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult> {
|
||||
return this._runAction(rowData.ownerUri, (runner) => {
|
||||
return runner.getQueryRows(rowData);
|
||||
|
||||
@@ -93,7 +93,7 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
//input.resolve().then(model => this.backupFileService.backupResource(resource, model.getValue(), model.getVersionId())).done(null, errors.onUnexpectedError);
|
||||
|
||||
const queryResultsInput: QueryResultsInput = this._instantiationService.createInstance(QueryResultsInput, docUri.toString());
|
||||
let queryInput: QueryInput = this._instantiationService.createInstance(QueryInput, fileInput.getName(), '', fileInput, queryResultsInput, connectionProviderName);
|
||||
let queryInput: QueryInput = this._instantiationService.createInstance(QueryInput, '', fileInput, queryResultsInput, connectionProviderName);
|
||||
|
||||
this._editorService.openEditor(queryInput, { pinned: true })
|
||||
.then((editor) => {
|
||||
@@ -113,7 +113,7 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
const self = this;
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
let queryPlanInput: QueryPlanInput = self._instantiationService.createInstance(QueryPlanInput, xmlShowPlan, 'aaa', undefined);
|
||||
self._editorService.openEditor(queryPlanInput, { pinned: true }, false);
|
||||
self._editorService.openEditor(queryPlanInput, { pinned: true }, false);
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
@@ -222,8 +222,7 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
}
|
||||
|
||||
let uri: URI = QueryEditorService._getEditorChangeUri(editor.input, changingToSql);
|
||||
if(uri.scheme === Schemas.untitled && (editor.input instanceof QueryInput || editor.input instanceof EditDataInput))
|
||||
{
|
||||
if (uri.scheme === Schemas.untitled && (editor.input instanceof QueryInput || editor.input instanceof EditDataInput)) {
|
||||
QueryEditorService.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: QueryEditorService.CHANGE_UNSUPPORTED_ERROR_MESSAGE
|
||||
@@ -258,15 +257,15 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
}
|
||||
|
||||
// Close the current editor
|
||||
QueryEditorService.editorService.closeEditor(position, editor.input).then(() => {
|
||||
QueryEditorService.editorService.closeEditor(position, editor.input).then(() => {
|
||||
|
||||
// Reopen a new editor in the same position/index
|
||||
QueryEditorService.editorService.openEditor(newEditorInput, options, position).then((editor) => {
|
||||
QueryEditorService.editorService.openEditor(newEditorInput, options, position).then((editor) => {
|
||||
resolve(QueryEditorService._onEditorOpened(editor, uri.toString(), position, options.pinned));
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
});
|
||||
(error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -331,10 +330,10 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
let newEditorInput: IEditorInput = undefined;
|
||||
if (changingToSql) {
|
||||
const queryResultsInput: QueryResultsInput = QueryEditorService.instantiationService.createInstance(QueryResultsInput, uri.toString());
|
||||
let queryInput: QueryInput = QueryEditorService.instantiationService.createInstance(QueryInput, input.getName(), '', input, queryResultsInput, undefined);
|
||||
let queryInput: QueryInput = QueryEditorService.instantiationService.createInstance(QueryInput, '', input, queryResultsInput, undefined);
|
||||
newEditorInput = queryInput;
|
||||
} else {
|
||||
let uriCopy: URI = URI.from( { scheme: uri.scheme, authority: uri.authority, path: uri.path, query: uri.query, fragment: uri.fragment } );
|
||||
let uriCopy: URI = URI.from({ scheme: uri.scheme, authority: uri.authority, path: uri.path, query: uri.query, fragment: uri.fragment });
|
||||
newEditorInput = QueryEditorService.instantiationService.createInstance(FileEditorInput, uriCopy, undefined);
|
||||
}
|
||||
|
||||
@@ -350,7 +349,7 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
// It is assumed that if we got here, !changingToSql is logically equivalent to changingFromSql
|
||||
let changingFromSql = !changingToSql;
|
||||
if (input instanceof QueryInput && changingFromSql) {
|
||||
let queryInput: QueryInput = <QueryInput> input;
|
||||
let queryInput: QueryInput = <QueryInput>input;
|
||||
uriSource = queryInput.sql;
|
||||
}
|
||||
return getSupportedInputResource(uriSource);
|
||||
@@ -378,7 +377,7 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
|
||||
// Grab and returns the IModel that will be used to resolve the sqlLanguageModeCheck promise.
|
||||
let control = editor.getControl();
|
||||
let codeEditor: CodeEditor = <CodeEditor> control;
|
||||
let codeEditor: CodeEditor = <CodeEditor>control;
|
||||
let newModel = codeEditor ? codeEditor.getModel() : undefined;
|
||||
return newModel;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtension } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
|
||||
import * as nls from 'vs/nls';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { IExtensionPointUser, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
|
||||
import { ProviderProperties } from 'sql/parts/dashboard/widgets/properties/propertiesWidget.component';
|
||||
@@ -22,11 +21,13 @@ export const Extensions = {
|
||||
export interface IDashboardTab {
|
||||
id: string;
|
||||
title: string;
|
||||
provider: string | string[];
|
||||
publisher: string;
|
||||
description?: string;
|
||||
container?: object;
|
||||
when?: string;
|
||||
alwaysShow?: boolean;
|
||||
isHomeTab?: boolean;
|
||||
}
|
||||
|
||||
export interface IDashboardRegistry {
|
||||
|
||||
@@ -26,10 +26,11 @@ import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
/* Model-backed components */
|
||||
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
|
||||
|
||||
export const DialogModule = (params, selector: string, instantiationService: IInstantiationService): any => {
|
||||
|
||||
/* Model-backed components */
|
||||
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
Checkbox,
|
||||
|
||||
@@ -6,29 +6,41 @@
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/dialogModal';
|
||||
import { Component, AfterContentInit, ViewChild, Input, Inject, forwardRef, ElementRef } from '@angular/core';
|
||||
import { Component, ViewChild, Inject, forwardRef, ElementRef, AfterViewInit } from '@angular/core';
|
||||
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { DialogPane } from 'sql/platform/dialog/dialogPane';
|
||||
import { ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ComponentEventType } from '../../parts/modelComponents/interfaces';
|
||||
|
||||
export interface DialogComponentParams extends IBootstrapParams {
|
||||
modelViewId: string;
|
||||
validityChangedCallback: (valid: boolean) => void;
|
||||
onLayoutRequested: Event<string>;
|
||||
dialogPane: DialogPane;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dialog-modelview-container',
|
||||
providers: [],
|
||||
template: `
|
||||
<modelview-content [modelViewId]="modelViewId">
|
||||
<div class="dialogContainer" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
|
||||
<div class="dialogModal-wizardHeader" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
|
||||
<div *ngIf="_dialogPane.pageNumber" class="wizardPageNumber">Step {{_dialogPane.pageNumber}}</div>
|
||||
<h1 class="wizardPageTitle">{{_dialogPane.title}}</h1>
|
||||
<div *ngIf="_dialogPane.description">{{_dialogPane.description}}</div>
|
||||
</div>
|
||||
<modelview-content [modelViewId]="modelViewId">
|
||||
</modelview-content>
|
||||
</div>
|
||||
<modelview-content [modelViewId]="modelViewId" *ngIf="!_dialogPane || !_dialogPane.displayPageTitle">
|
||||
</modelview-content>
|
||||
`
|
||||
})
|
||||
export class DialogContainer implements AfterContentInit {
|
||||
export class DialogContainer implements AfterViewInit {
|
||||
private _onResize = new Emitter<void>();
|
||||
public readonly onResize: Event<void> = this._onResize.event;
|
||||
private _dialogPane: DialogPane;
|
||||
|
||||
public modelViewId: string;
|
||||
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
|
||||
@@ -41,11 +53,12 @@ export class DialogContainer implements AfterContentInit {
|
||||
this.layout();
|
||||
}
|
||||
});
|
||||
this._dialogPane = this._params.dialogPane;
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
ngAfterViewInit(): void {
|
||||
this._modelViewContent.onEvent(event => {
|
||||
if (event.eventType === ComponentEventType.validityChanged) {
|
||||
if (event.isRootComponent && event.eventType === ComponentEventType.validityChanged) {
|
||||
this._params.validityChangedCallback(event.args);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ export class DialogModal extends Modal {
|
||||
});
|
||||
|
||||
this._dialogPane = new DialogPane(this._dialog.title, this._dialog.content,
|
||||
valid => this._dialog.notifyValidityChanged(valid), this._instantiationService);
|
||||
valid => this._dialog.notifyValidityChanged(valid), this._instantiationService, false);
|
||||
this._dialogPane.createBody(body);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,11 @@ import 'vs/css!./media/dialogModal';
|
||||
import { NgModuleRef } from '@angular/core';
|
||||
|
||||
import { IModalDialogStyles } from 'sql/base/browser/ui/modal/modal';
|
||||
import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes';
|
||||
import { DialogTab } from 'sql/platform/dialog/dialogTypes';
|
||||
import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { DialogModule } from 'sql/platform/dialog/dialog.module';
|
||||
import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.component';
|
||||
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
@@ -31,24 +30,21 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
// Validation
|
||||
private _modelViewValidityMap = new Map<string, boolean>();
|
||||
|
||||
// HTML Elements
|
||||
private _body: HTMLElement;
|
||||
private _tabBar: HTMLElement;
|
||||
private _tabs: HTMLElement[];
|
||||
private _tabContent: HTMLElement[];
|
||||
private _selectedTabIndex: number = 0; //TODO: can be an option
|
||||
private _onTabChange = new Emitter<string>();
|
||||
private _selectedTabContent: string;
|
||||
public pageNumber?: number;
|
||||
|
||||
constructor(
|
||||
private _title: string,
|
||||
public title: string,
|
||||
private _content: string | DialogTab[],
|
||||
private _validityChangedCallback: (valid: boolean) => void,
|
||||
private _instantiationService: IInstantiationService
|
||||
private _instantiationService: IInstantiationService,
|
||||
public displayPageTitle: boolean,
|
||||
public description?: string,
|
||||
) {
|
||||
super();
|
||||
this._tabs = [];
|
||||
this._tabContent = [];
|
||||
}
|
||||
|
||||
public createBody(container: HTMLElement): HTMLElement {
|
||||
@@ -73,7 +69,7 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
});
|
||||
this._tabbedPanel.pushTab({
|
||||
title: tab.title,
|
||||
identifier: 'dialogPane.' + this._title + '.' + tabIndex,
|
||||
identifier: 'dialogPane.' + this.title + '.' + tabIndex,
|
||||
view: {
|
||||
render: (container) => {
|
||||
if (tabContainer.parentElement === this._body) {
|
||||
@@ -93,12 +89,12 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
private getTabDimension(): DOM.Dimension {
|
||||
return new DOM.Dimension(DOM.getContentWidth(this._body), DOM.getContentHeight(this._body));
|
||||
return new DOM.Dimension(DOM.getContentWidth(this._body) - 5, DOM.getContentHeight(this._body) - 5);
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
if (this._tabbedPanel) {
|
||||
this._tabbedPanel.layout(new DOM.Dimension(DOM.getContentWidth(this._body), DOM.getContentHeight(this._body)));
|
||||
this._tabbedPanel.layout(this.getTabDimension());
|
||||
this._onTabChange.fire(this._selectedTabContent);
|
||||
}
|
||||
}
|
||||
@@ -119,7 +115,8 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
tab.notifyValidityChanged(valid);
|
||||
}
|
||||
},
|
||||
onLayoutRequested: this._onTabChange.event
|
||||
onLayoutRequested: this._onTabChange.event,
|
||||
dialogPane: this
|
||||
} as DialogComponentParams,
|
||||
undefined,
|
||||
(moduleRef) => {
|
||||
|
||||
@@ -135,6 +135,7 @@ export class DialogButton implements sqlops.window.modelviewdialog.Button {
|
||||
export class WizardPage extends DialogTab {
|
||||
public customButtons: DialogButton[];
|
||||
private _enabled: boolean;
|
||||
private _description: string;
|
||||
private _onUpdate: Emitter<void> = new Emitter<void>();
|
||||
public readonly onUpdate: Event<void> = this._onUpdate.event;
|
||||
|
||||
@@ -150,6 +151,15 @@ export class WizardPage extends DialogTab {
|
||||
this._enabled = enabled;
|
||||
this._onUpdate.fire();
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return this._description;
|
||||
}
|
||||
|
||||
public set description(description: string) {
|
||||
this._description = description;
|
||||
this._onUpdate.fire();
|
||||
}
|
||||
}
|
||||
|
||||
export class Wizard {
|
||||
@@ -171,6 +181,7 @@ export class Wizard {
|
||||
private _onMessageChange = new Emitter<DialogMessage>();
|
||||
public readonly onMessageChange = this._onMessageChange.event;
|
||||
private _message: DialogMessage;
|
||||
public displayPageTitles: boolean;
|
||||
|
||||
constructor(public title: string) { }
|
||||
|
||||
|
||||
@@ -30,3 +30,19 @@
|
||||
.footer-button.dialogModal-hidden {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dialogModal-wizardHeader {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
.dialogModal-wizardHeader h1 {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 3px;
|
||||
font-size: 1.5em;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
.dialogContainer {
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
@@ -133,18 +133,28 @@ export class WizardModal extends Modal {
|
||||
});
|
||||
this._wizard.onPageAdded(page => {
|
||||
this.registerPage(page);
|
||||
this.updatePageNumbers();
|
||||
this.showPage(this._wizard.currentPage, false);
|
||||
});
|
||||
this._wizard.onPageRemoved(page => {
|
||||
let dialogPane = this._dialogPanes.get(page);
|
||||
this._dialogPanes.delete(page);
|
||||
this.updatePageNumbers();
|
||||
this.showPage(this._wizard.currentPage, false);
|
||||
dialogPane.dispose();
|
||||
});
|
||||
this.updatePageNumbers();
|
||||
}
|
||||
|
||||
private updatePageNumbers(): void {
|
||||
this._wizard.pages.forEach((page, index) => {
|
||||
let dialogPane = this._dialogPanes.get(page);
|
||||
dialogPane.pageNumber = index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
private registerPage(page: WizardPage): void {
|
||||
let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService);
|
||||
let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService, this._wizard.displayPageTitles, page.description);
|
||||
dialogPane.createBody(this._body);
|
||||
this._dialogPanes.set(page, dialogPane);
|
||||
page.onUpdate(() => this.setButtonsForPage(this._wizard.currentPage));
|
||||
|
||||
@@ -63,7 +63,7 @@ export class MetadataService implements IMetadataService {
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
public getTableInfo(connectionUri: string, metadata: sqlops.ObjectMetadata): Thenable<sqlops.ColumnMetadata[]> {
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IComponentEventArgs } from 'sql/parts/modelComponents/interfaces';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IView {
|
||||
readonly id: string;
|
||||
@@ -14,6 +15,10 @@ export interface IView {
|
||||
readonly serverInfo: sqlops.ServerInfo;
|
||||
}
|
||||
|
||||
export interface IModelViewEventArgs extends IComponentEventArgs {
|
||||
isRootComponent: boolean;
|
||||
}
|
||||
|
||||
export interface IModelView extends IView {
|
||||
initializeModel(rootComponent: IComponentShape, validationCallback?: (componentId: string) => Thenable<boolean>): void;
|
||||
clearContainer(componentId: string): void;
|
||||
@@ -21,7 +26,7 @@ export interface IModelView extends IView {
|
||||
setLayout(componentId: string, layout: any): void;
|
||||
setProperties(componentId: string, properties: { [key: string]: any }): void;
|
||||
registerEvent(componentId: string);
|
||||
onEvent: Event<any>;
|
||||
onEvent: Event<IModelViewEventArgs>;
|
||||
validate(componentId: string): Thenable<boolean>;
|
||||
readonly onDestroy: Event<void>;
|
||||
}
|
||||
|
||||
152
src/sql/sqlops.d.ts
vendored
@@ -635,6 +635,7 @@ declare module 'sqlops' {
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<SimpleExecuteResult>;
|
||||
parseSyntax(ownerUri: string, query: string): Thenable<SyntaxParseResult>;
|
||||
getQueryRows(rowData: QueryExecuteSubsetParams): Thenable<QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Thenable<void>;
|
||||
saveResults(requestParams: SaveResultsRequestParams): Thenable<SaveResultRequestResult>;
|
||||
@@ -769,6 +770,11 @@ declare module 'sqlops' {
|
||||
rows: DbCellValue[][];
|
||||
}
|
||||
|
||||
export interface SyntaxParseResult {
|
||||
parseable: boolean;
|
||||
errorMessages: string[];
|
||||
}
|
||||
|
||||
// Query Batch Notification -----------------------------------------------------------------------
|
||||
export interface QueryExecuteBatchNotificationParams {
|
||||
batchSummary: BatchSummary;
|
||||
@@ -1054,8 +1060,44 @@ declare module 'sqlops' {
|
||||
wmiEvent = 4
|
||||
}
|
||||
|
||||
export enum JobCompletionActionCondition {
|
||||
Never = 0,
|
||||
OnSuccess = 1,
|
||||
OnFailure = 2,
|
||||
Always = 3
|
||||
}
|
||||
|
||||
export enum FrequencyTypes {
|
||||
Unknown ,
|
||||
OneTime = 1 << 1,
|
||||
Daily = 1 << 2,
|
||||
Weekly = 1 << 3,
|
||||
Monthly = 1 << 4,
|
||||
MonthlyRelative = 1 << 5,
|
||||
AutoStart = 1 << 6,
|
||||
OnIdle = 1 << 7
|
||||
}
|
||||
|
||||
export enum FrequencySubDayTypes {
|
||||
Unknown = 0,
|
||||
Once = 1,
|
||||
Second = 2,
|
||||
Minute = 4,
|
||||
Hour = 8
|
||||
}
|
||||
|
||||
export enum FrequencyRelativeIntervals {
|
||||
First = 1,
|
||||
Second = 2,
|
||||
Third = 4,
|
||||
Fourth = 8,
|
||||
Last = 16
|
||||
}
|
||||
|
||||
export interface AgentJobInfo {
|
||||
name: string;
|
||||
owner: string;
|
||||
description: string;
|
||||
currentExecutionStatus: number;
|
||||
lastRunOutcome: number;
|
||||
currentExecutionStep: string;
|
||||
@@ -1070,9 +1112,38 @@ declare module 'sqlops' {
|
||||
lastRun: string;
|
||||
nextRun: string;
|
||||
jobId: string;
|
||||
EmailLevel: JobCompletionActionCondition;
|
||||
PageLevel: JobCompletionActionCondition;
|
||||
EventLogLevel: JobCompletionActionCondition;
|
||||
DeleteLevel: JobCompletionActionCondition;
|
||||
OperatorToEmail: string;
|
||||
OperatorToPage: string;
|
||||
JobSteps: AgentJobStepInfo[];
|
||||
JobSchedules: AgentJobScheduleInfo[];
|
||||
Alerts: AgentAlertInfo[];
|
||||
}
|
||||
|
||||
export interface AgentJobStepInfo {
|
||||
export interface AgentJobScheduleInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
jobName: string;
|
||||
isEnabled: boolean;
|
||||
frequencyTypes: FrequencyTypes;
|
||||
frequencySubDayTypes: FrequencySubDayTypes;
|
||||
frequencySubDayInterval: number;
|
||||
frequencyRelativeIntervals; FrequencyRelativeIntervals;
|
||||
frequencyRecurrenceFactor: number;
|
||||
frequencyInterval: number;
|
||||
dateCreated: string;
|
||||
activeStartTimeOfDay: string;
|
||||
activeStartDate: string;
|
||||
activeEndTimeOfDay: string;
|
||||
jobCount: number;
|
||||
activeEndDate: string;
|
||||
scheduleUid: string;
|
||||
}
|
||||
|
||||
export interface AgentJobStep {
|
||||
jobId: string;
|
||||
stepId: string;
|
||||
stepName: string;
|
||||
@@ -1081,6 +1152,33 @@ declare module 'sqlops' {
|
||||
runStatus: number;
|
||||
}
|
||||
|
||||
export interface AgentJobStepInfo {
|
||||
jobId: string;
|
||||
jobName: string;
|
||||
script: string;
|
||||
scriptName: string;
|
||||
stepName: string;
|
||||
subSystem: string;
|
||||
id: number;
|
||||
failureAction: string;
|
||||
successAction: string;
|
||||
failStepId: number;
|
||||
successStepId: number;
|
||||
command: string;
|
||||
commandExecutionSuccessCode: number;
|
||||
databaseName: string;
|
||||
databaseUserName: string;
|
||||
server: string;
|
||||
outputFileName: string;
|
||||
appendToLogFile: boolean;
|
||||
appendToStepHist: boolean;
|
||||
writeLogToTable: boolean;
|
||||
appendLogToTable: boolean;
|
||||
retryAttempts: number;
|
||||
retryInterval: number;
|
||||
proxyName: string;
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryInfo {
|
||||
instanceId: number;
|
||||
sqlMessageId: string;
|
||||
@@ -1098,7 +1196,7 @@ declare module 'sqlops' {
|
||||
operatorPaged: string;
|
||||
retriesAttempted: string;
|
||||
server: string;
|
||||
steps: AgentJobStepInfo[];
|
||||
steps: AgentJobStep[];
|
||||
}
|
||||
|
||||
export interface AgentProxyInfo {
|
||||
@@ -1113,6 +1211,7 @@ declare module 'sqlops' {
|
||||
|
||||
export interface AgentAlertInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
delayBetweenResponses: number;
|
||||
eventDescriptionKeyword: string;
|
||||
eventSource: string;
|
||||
@@ -1173,15 +1272,26 @@ declare module 'sqlops' {
|
||||
job: AgentJobInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentJobResult extends ResultStatus {
|
||||
export interface UpdateAgentJobResult extends ResultStatus {
|
||||
job: AgentJobInfo;
|
||||
}
|
||||
|
||||
export interface AgentJobCategory
|
||||
{
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AgentJobDefaultsResult extends ResultStatus {
|
||||
owner: string;
|
||||
categories: AgentJobCategory[];
|
||||
}
|
||||
|
||||
export interface CreateAgentJobStepResult extends ResultStatus {
|
||||
step: AgentJobStepInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentJobStepResult extends ResultStatus {
|
||||
export interface UpdateAgentJobStepResult extends ResultStatus {
|
||||
step: AgentJobStepInfo;
|
||||
}
|
||||
|
||||
@@ -1189,7 +1299,7 @@ declare module 'sqlops' {
|
||||
step: AgentJobStepInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentProxyResult extends ResultStatus {
|
||||
export interface UpdateAgentProxyResult extends ResultStatus {
|
||||
step: AgentJobStepInfo;
|
||||
}
|
||||
|
||||
@@ -1201,7 +1311,7 @@ declare module 'sqlops' {
|
||||
alert: AgentJobStepInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentAlertResult extends ResultStatus {
|
||||
export interface UpdateAgentAlertResult extends ResultStatus {
|
||||
alert: AgentJobStepInfo;
|
||||
}
|
||||
|
||||
@@ -1213,20 +1323,32 @@ declare module 'sqlops' {
|
||||
operator: AgentOperatorInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentOperatorResult extends ResultStatus {
|
||||
export interface UpdateAgentOperatorResult extends ResultStatus {
|
||||
operator: AgentOperatorInfo;
|
||||
}
|
||||
|
||||
export interface AgentProxiesResult extends ResultStatus {
|
||||
operators: AgentOperatorInfo[];
|
||||
proxies: AgentProxyInfo[];
|
||||
}
|
||||
|
||||
export interface CreateAgentProxyResult extends ResultStatus {
|
||||
operator: AgentOperatorInfo;
|
||||
proxy: AgentProxyInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentProxyResult extends ResultStatus {
|
||||
operator: AgentOperatorInfo;
|
||||
export interface UpdateAgentProxyResult extends ResultStatus {
|
||||
proxy: AgentProxyInfo;
|
||||
}
|
||||
|
||||
export interface AgentJobSchedulesResult extends ResultStatus {
|
||||
schedules: AgentJobScheduleInfo[];
|
||||
}
|
||||
|
||||
export interface CreateAgentJobScheduleResult extends ResultStatus {
|
||||
schedule: AgentJobScheduleInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentJobScheduleResult extends ResultStatus {
|
||||
schedule: AgentJobScheduleInfo;
|
||||
}
|
||||
|
||||
export interface AgentServicesProvider extends DataProvider {
|
||||
@@ -1237,6 +1359,7 @@ declare module 'sqlops' {
|
||||
createJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable<CreateAgentJobResult>;
|
||||
updateJob(ownerUri: string, originalJobName: string, jobInfo: AgentJobInfo): Thenable<UpdateAgentJobResult>;
|
||||
deleteJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable<ResultStatus>;
|
||||
getJobDefaults(ownerUri: string): Thenable<AgentJobDefaultsResult>;
|
||||
|
||||
// Job Step management methods
|
||||
createJobStep(ownerUri: string, jobInfo: AgentJobStepInfo): Thenable<CreateAgentJobStepResult>;
|
||||
@@ -1260,6 +1383,13 @@ declare module 'sqlops' {
|
||||
createProxy(ownerUri: string, proxyInfo: AgentProxyInfo): Thenable<CreateAgentOperatorResult>;
|
||||
updateProxy(ownerUri: string, originalProxyName: string, proxyInfo: AgentProxyInfo): Thenable<UpdateAgentOperatorResult>;
|
||||
deleteProxy(ownerUri: string, proxyInfo: AgentProxyInfo): Thenable<ResultStatus>;
|
||||
|
||||
|
||||
// Job Schedule management methods
|
||||
getJobSchedules(ownerUri: string): Thenable<AgentJobSchedulesResult>;
|
||||
createJobSchedule(ownerUri: string, scheduleInfo: AgentJobScheduleInfo): Thenable<CreateAgentJobScheduleResult>;
|
||||
updateJobSchedule(ownerUri: string, originalScheduleName: string, scheduleInfo: AgentJobScheduleInfo): Thenable<UpdateAgentJobScheduleResult>;
|
||||
deleteJobSchedule(ownerUri: string, scheduleInfo: AgentJobScheduleInfo): Thenable<ResultStatus>;
|
||||
}
|
||||
|
||||
// Task service interfaces ----------------------------------------------------------------------------
|
||||
|
||||
27
src/sql/sqlops.proposed.d.ts
vendored
@@ -133,6 +133,7 @@ declare module 'sqlops' {
|
||||
component: Component;
|
||||
title: string;
|
||||
actions?: Component[];
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolbarComponent {
|
||||
@@ -240,7 +241,6 @@ declare module 'sqlops' {
|
||||
componentWidth?: number | string;
|
||||
componentHeight?: number | string;
|
||||
titleFontSize?: number | string;
|
||||
required?: boolean;
|
||||
info?: string;
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ declare module 'sqlops' {
|
||||
}
|
||||
|
||||
export enum CardType {
|
||||
VerticalButton = 'VerticalButton',
|
||||
VerticalButton = 'VerticalButton',
|
||||
Details = 'Details'
|
||||
}
|
||||
|
||||
@@ -314,8 +314,16 @@ declare module 'sqlops' {
|
||||
value?: string;
|
||||
actions?: ActionDescriptor[];
|
||||
status?: StatusIndicator;
|
||||
|
||||
/**
|
||||
* Returns true if the card is selected
|
||||
*/
|
||||
selected?: boolean;
|
||||
cardType: CardType;
|
||||
|
||||
/**
|
||||
* Card Type, default: Details
|
||||
*/
|
||||
cardType?: CardType;
|
||||
}
|
||||
|
||||
export type InputBoxInputType = 'color' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'range' | 'search' | 'text' | 'time' | 'url' | 'week';
|
||||
@@ -748,13 +756,18 @@ declare module 'sqlops' {
|
||||
* able to advance to it. Defaults to true.
|
||||
*/
|
||||
enabled: boolean;
|
||||
|
||||
/**
|
||||
* An optional description for the page. If provided it will be displayed underneath the page title.
|
||||
*/
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface Wizard {
|
||||
/**
|
||||
* The title of the wizard
|
||||
*/
|
||||
title: string,
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* The wizard's pages. Pages can be added/removed while the dialog is open by using
|
||||
@@ -800,6 +813,12 @@ declare module 'sqlops' {
|
||||
*/
|
||||
customButtons: Button[];
|
||||
|
||||
/**
|
||||
* When set to false page titles and descriptions will not be displayed at the top
|
||||
* of each wizard page. The default is true.
|
||||
*/
|
||||
displayPageTitles: boolean;
|
||||
|
||||
/**
|
||||
* Event fired when the wizard's page changes, containing information about the
|
||||
* previous page and the new page
|
||||
|
||||
@@ -63,8 +63,7 @@ export enum ScriptOperation {
|
||||
Alter = 6
|
||||
}
|
||||
|
||||
export enum WeekDays
|
||||
{
|
||||
export enum WeekDays {
|
||||
sunday = 1,
|
||||
monday = 2,
|
||||
tuesday = 4,
|
||||
@@ -77,8 +76,7 @@ export enum WeekDays
|
||||
everyDay = 127
|
||||
}
|
||||
|
||||
export enum NotifyMethods
|
||||
{
|
||||
export enum NotifyMethods {
|
||||
none = 0,
|
||||
notifyEmail = 1,
|
||||
pager = 2,
|
||||
@@ -86,14 +84,47 @@ export enum NotifyMethods
|
||||
notifyAll = 7
|
||||
}
|
||||
|
||||
export enum AlertType
|
||||
{
|
||||
export enum JobCompletionActionCondition {
|
||||
Never = 0,
|
||||
OnSuccess = 1,
|
||||
OnFailure = 2,
|
||||
Always = 3
|
||||
}
|
||||
|
||||
export enum AlertType {
|
||||
sqlServerEvent = 1,
|
||||
sqlServerPerformanceCondition = 2,
|
||||
nonSqlServerEvent = 3,
|
||||
wmiEvent = 4
|
||||
}
|
||||
|
||||
export enum FrequencyTypes {
|
||||
Unknown ,
|
||||
OneTime = 1 << 1,
|
||||
Daily = 1 << 2,
|
||||
Weekly = 1 << 3,
|
||||
Monthly = 1 << 4,
|
||||
MonthlyRelative = 1 << 5,
|
||||
AutoStart = 1 << 6,
|
||||
OnIdle = 1 << 7
|
||||
}
|
||||
|
||||
export enum FrequencySubDayTypes {
|
||||
Unknown = 0,
|
||||
Once = 1,
|
||||
Second = 2,
|
||||
Minute = 4,
|
||||
Hour = 8
|
||||
}
|
||||
|
||||
export enum FrequencyRelativeIntervals {
|
||||
First = 1,
|
||||
Second = 2,
|
||||
Third = 4,
|
||||
Fourth = 8,
|
||||
Last = 16
|
||||
}
|
||||
|
||||
export enum ModelComponentTypes {
|
||||
NavContainer,
|
||||
FlexContainer,
|
||||
@@ -168,6 +199,7 @@ export interface IModelViewWizardPageDetails {
|
||||
content: string;
|
||||
enabled: boolean;
|
||||
customButtons: number[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface IModelViewWizardDetails {
|
||||
@@ -181,6 +213,7 @@ export interface IModelViewWizardDetails {
|
||||
backButton: number;
|
||||
customButtons: number[];
|
||||
message: DialogMessage;
|
||||
displayPageTitles: boolean;
|
||||
}
|
||||
|
||||
export enum MessageLevel {
|
||||
|
||||