Layering of everything else but query (#5085)
* layer profiler and edit data * relayering everything but query * fix css import * readd qp * fix script src * fix hygiene
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<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>
|
||||
@@ -0,0 +1,119 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/jobs';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Component, Inject, forwardRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core';
|
||||
import { AgentJobInfo } from 'azdata';
|
||||
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
|
||||
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
|
||||
|
||||
export const DASHBOARD_SELECTOR: string = 'agentview-component';
|
||||
|
||||
@Component({
|
||||
selector: DASHBOARD_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./agentView.component.html'))
|
||||
})
|
||||
@Injectable()
|
||||
export class AgentViewComponent {
|
||||
|
||||
@ViewChild(PanelComponent) private _panel: PanelComponent;
|
||||
|
||||
private _showHistory: boolean = false;
|
||||
private _jobId: string = null;
|
||||
private _agentJobInfo: AgentJobInfo = null;
|
||||
private _refresh: boolean = undefined;
|
||||
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';
|
||||
|
||||
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");
|
||||
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
private readonly panelOpt: IPanelOptions = {
|
||||
showTabsWhenOne: true,
|
||||
layout: NavigationBarLayout.vertical,
|
||||
showIcon: true
|
||||
};
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(IJobManagementService) jobManagementService: IJobManagementService,
|
||||
@Inject(IDashboardService) dashboardService: IDashboardService, ) {
|
||||
this._expanded = new Map<string, string>();
|
||||
|
||||
let self = this;
|
||||
jobManagementService.onDidChange((args) => {
|
||||
self.refresh = true;
|
||||
self._cd.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Getters
|
||||
*/
|
||||
public get jobId(): string {
|
||||
return this._jobId;
|
||||
}
|
||||
|
||||
public get showHistory(): boolean {
|
||||
return this._showHistory;
|
||||
}
|
||||
|
||||
public get agentJobInfo(): AgentJobInfo {
|
||||
return this._agentJobInfo;
|
||||
}
|
||||
|
||||
public get refresh(): boolean {
|
||||
return this._refresh;
|
||||
}
|
||||
|
||||
public get expanded(): Map<string, string> {
|
||||
return this._expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Setters
|
||||
*/
|
||||
|
||||
public set jobId(value: string) {
|
||||
this._jobId = value;
|
||||
}
|
||||
|
||||
public set showHistory(value: boolean) {
|
||||
this._showHistory = value;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
public set agentJobInfo(value: AgentJobInfo) {
|
||||
this._agentJobInfo = value;
|
||||
}
|
||||
|
||||
public set refresh(value: boolean) {
|
||||
this._refresh = value;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
public setExpanded(jobId: string, errorMessage: string) {
|
||||
this._expanded.set(jobId, errorMessage);
|
||||
}
|
||||
|
||||
public set expanded(value: Map<string, string>) {
|
||||
this._expanded = value;
|
||||
}
|
||||
|
||||
public layout() {
|
||||
this._panel.layout();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 #actionbarContainer class="agent-actionbar-container"></div>
|
||||
|
||||
<div #jobalertsgrid class="jobalertsview-grid"></div>
|
||||
@@ -0,0 +1,226 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/jobs';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as azdata from 'azdata';
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/electron-browser/agentView.component';
|
||||
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
|
||||
import { EditAlertAction, DeleteAlertAction, NewAlertAction } from 'sql/platform/jobManagement/common/jobActions';
|
||||
import { JobManagementView } from 'sql/workbench/parts/jobManagement/electron-browser/jobManagementView';
|
||||
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
import { AlertsCacheObject } from 'sql/platform/jobManagement/common/jobManagementService';
|
||||
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowDetailView';
|
||||
|
||||
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 extends JobManagementView implements OnInit, OnDestroy {
|
||||
|
||||
private columns: Array<Slick.Column<any>> = [
|
||||
{
|
||||
name: nls.localize('jobAlertColumns.name', 'Name'),
|
||||
field: 'name',
|
||||
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
|
||||
width: 500,
|
||||
id: 'name'
|
||||
},
|
||||
{ name: nls.localize('jobAlertColumns.lastOccurrenceDate', 'Last Occurrence'), field: 'lastOccurrenceDate', width: 150, id: 'lastOccurrenceDate' },
|
||||
{ name: nls.localize('jobAlertColumns.enabled', 'Enabled'), field: 'enabled', width: 80, id: 'enabled' },
|
||||
{ name: nls.localize('jobAlertColumns.delayBetweenResponses', 'Delay Between Responses (in secs)'), field: 'delayBetweenResponses', width: 200, id: 'delayBetweenResponses' },
|
||||
{ name: nls.localize('jobAlertColumns.categoryName', 'Category Name'), field: 'categoryName', width: 250, id: 'categoryName' },
|
||||
];
|
||||
|
||||
private options: Slick.GridOptions<any> = {
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: ROW_HEIGHT,
|
||||
enableCellNavigation: true,
|
||||
editable: false
|
||||
};
|
||||
|
||||
private dataView: any;
|
||||
private _isCloud: boolean;
|
||||
private _alertsCacheObject: AlertsCacheObject;
|
||||
|
||||
private _didTabChange: boolean;
|
||||
@ViewChild('jobalertsgrid') _gridEl: ElementRef;
|
||||
|
||||
public alerts: azdata.AgentAlertInfo[];
|
||||
public contextAction = NewAlertAction;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService,
|
||||
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
|
||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||
@Inject(IDashboardService) _dashboardService: IDashboardService) {
|
||||
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService, _agentViewComponent);
|
||||
this._didTabChange = false;
|
||||
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
|
||||
let alertsCacheObjectMap = this._jobManagementService.alertsCacheObjectMap;
|
||||
let alertsCache = alertsCacheObjectMap[this._serverName];
|
||||
if (alertsCache) {
|
||||
this._alertsCacheObject = alertsCache;
|
||||
} else {
|
||||
this._alertsCacheObject = new AlertsCacheObject();
|
||||
this._alertsCacheObject.serverName = this._serverName;
|
||||
this._jobManagementService.addToCache(this._serverName, this._alertsCacheObject);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// set base class elements
|
||||
this._visibilityElement = this._gridEl;
|
||||
this._parentComponent = this._agentViewComponent;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._didTabChange = true;
|
||||
}
|
||||
|
||||
public layout() {
|
||||
let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
|
||||
if (height < 0) {
|
||||
height = 0;
|
||||
}
|
||||
|
||||
if (this._table) {
|
||||
this._table.layout(new dom.Dimension(
|
||||
dom.getContentWidth(this._gridEl.nativeElement),
|
||||
height));
|
||||
}
|
||||
}
|
||||
|
||||
onFirstVisible() {
|
||||
let self = this;
|
||||
let cached: boolean = false;
|
||||
if (this._alertsCacheObject.serverName === this._serverName) {
|
||||
if (this._alertsCacheObject.alerts && this._alertsCacheObject.alerts.length > 0) {
|
||||
cached = true;
|
||||
this.alerts = this._alertsCacheObject.alerts;
|
||||
}
|
||||
}
|
||||
|
||||
let columns = this.columns.map((column) => {
|
||||
column.rerenderOnResize = true;
|
||||
return column;
|
||||
});
|
||||
|
||||
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
|
||||
let rowDetail = new RowDetailView({
|
||||
cssClass: '_detail_selector',
|
||||
useRowClick: false,
|
||||
panelRows: 1
|
||||
});
|
||||
columns.unshift(rowDetail.getColumnDefinition());
|
||||
jQuery(this._gridEl.nativeElement).empty();
|
||||
jQuery(this.actionBarContainer.nativeElement).empty();
|
||||
this.initActionBar();
|
||||
|
||||
this._table = new Table(this._gridEl.nativeElement, { columns }, this.options);
|
||||
this._table.grid.setData(this.dataView, true);
|
||||
this._register(this._table.onContextMenu(e => {
|
||||
self.openContextMenu(e);
|
||||
}));
|
||||
|
||||
// check for cached state
|
||||
if (cached && this._agentViewComponent.refresh !== true) {
|
||||
self.onAlertsAvailable(this.alerts);
|
||||
this._showProgressWheel = false;
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
} else {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._jobManagementService.getAlerts(ownerUri).then((result) => {
|
||||
if (result && result.alerts) {
|
||||
self.alerts = result.alerts;
|
||||
self._alertsCacheObject.alerts = result.alerts;
|
||||
self.onAlertsAvailable(result.alerts);
|
||||
} else {
|
||||
// TODO: handle error
|
||||
}
|
||||
this._showProgressWheel = false;
|
||||
if (this.isVisible && !this._didTabChange) {
|
||||
this._cd.detectChanges();
|
||||
} else if (this._didTabChange) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onAlertsAvailable(alerts: azdata.AgentAlertInfo[]) {
|
||||
let items: any = alerts.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
lastOccurrenceDate: item.lastOccurrenceDate,
|
||||
enabled: item.isEnabled,
|
||||
delayBetweenResponses: item.delayBetweenResponses,
|
||||
categoryName: item.categoryName
|
||||
};
|
||||
});
|
||||
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(items);
|
||||
this.dataView.endUpdate();
|
||||
this._alertsCacheObject.dataview = this.dataView;
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
}
|
||||
|
||||
protected getTableActions(targetObject: any): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
actions.push(this._instantiationService.createInstance(EditAlertAction));
|
||||
actions.push(this._instantiationService.createInstance(DeleteAlertAction));
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected getCurrentTableObject(rowIndex: number): any {
|
||||
let targetObject = {
|
||||
alertInfo: this.alerts && this.alerts.length >= rowIndex ? this.alerts[rowIndex] : undefined
|
||||
};
|
||||
return targetObject;
|
||||
}
|
||||
|
||||
|
||||
private renderName(row, cell, value, columnDef, dataContext) {
|
||||
let resultIndicatorClass = dataContext.enabled ? 'alertview-alertnameindicatorenabled' :
|
||||
'alertview-alertnameindicatordisabled';
|
||||
|
||||
return '<table class="alertview-alertnametable"><tr class="alertview-alertnamerow">' +
|
||||
'<td nowrap class=' + resultIndicatorClass + '></td>' +
|
||||
'<td nowrap class="alertview-alertnametext">' + dataContext.name + '</td>' +
|
||||
'</tr></table>';
|
||||
}
|
||||
|
||||
public openCreateAlertDialog() {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._commandService.executeCommand('agent.openAlertDialog', ownerUri, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div class="jobhistory-heading-container">
|
||||
<h1 class="job-heading">Jobs | {{this._agentJobInfo?.name}} </h1>
|
||||
<div class="icon in-progress" *ngIf="showProgressWheel()"></div>
|
||||
</div>
|
||||
|
||||
<!-- Back -->
|
||||
<div class="all-jobs">
|
||||
<div class="back-button-icon" (click)="goToJobs()"></div>All Jobs
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div #actionbarContainer class="agent-actionbar-container"></div>
|
||||
|
||||
<!-- Overview -->
|
||||
<div class="overview-container">
|
||||
<div class="overview-tab" (click)='toggleCollapse()' tabindex="0">
|
||||
<input id="accordion" type="checkbox">
|
||||
<label for="accordion">
|
||||
<div class="resultsViewCollapsible collapsed" (click)='toggleCollapse()'></div>
|
||||
Overview
|
||||
</label>
|
||||
<div class="accordion-content">
|
||||
<table align='left'>
|
||||
<tr>
|
||||
<td id='col1'>
|
||||
Category:
|
||||
</td>
|
||||
<td id='col2'>
|
||||
{{this._agentJobInfo?.category}}
|
||||
</td>
|
||||
<td id='col3'>
|
||||
Enabled:
|
||||
</td>
|
||||
<td id='col4'>
|
||||
{{this._agentJobInfo?.enabled}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id='col1'>
|
||||
Has Alert:
|
||||
</td>
|
||||
<td id='col2'>
|
||||
{{this._agentJobInfo?.hasTarget}}
|
||||
</td>
|
||||
<td id='col3'>
|
||||
Has Schedule:
|
||||
</td>
|
||||
<td id='col4'>
|
||||
{{this._agentJobInfo?.hasSchedule}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id='col1'>
|
||||
Last Run:
|
||||
</td>
|
||||
<td id='col2'>
|
||||
{{this._agentJobInfo?.lastRun}}
|
||||
</td>
|
||||
<td id='col3'>
|
||||
Next Run:
|
||||
</td>
|
||||
<td id='col4'>
|
||||
{{this._agentJobInfo?.nextRun}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Job History details -->
|
||||
<div class='history-details'>
|
||||
<!-- Previous run list -->
|
||||
<div class="prev-run-list-container" style="min-width: 270px">
|
||||
<table *ngIf="_showPreviousRuns === true">
|
||||
<tr>
|
||||
<td class="date-column">
|
||||
<b>Date</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Status</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h3 *ngIf="_showPreviousRuns === false" style="text-align: center">No Previous Runs Available</h3>
|
||||
<div class="step-table prev-run-list" style="position: relative; width: 100%">
|
||||
<div #table style="position: absolute; width: 100%; height: 100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Job Steps -->
|
||||
<div class="job-steps" id="job-steps">
|
||||
<h1 class="job-heading">
|
||||
{{agentJobHistoryInfo?.runDate}}
|
||||
</h1>
|
||||
<table class="step-list">
|
||||
<tr class="step-row">
|
||||
<td height="20">
|
||||
<h3>Status:</h3>
|
||||
</td>
|
||||
<td height="20">
|
||||
<h3>{{_runStatus}}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="step-row">
|
||||
<td height="20">
|
||||
Job ID:
|
||||
</td>
|
||||
<td height="20" style="user-select: initial">
|
||||
{{agentJobHistoryInfo?.jobId || agentJobInfo?.jobId}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="step-row">
|
||||
<td height="20">
|
||||
Message:
|
||||
</td>
|
||||
<td height="20" style="user-select: initial">
|
||||
{{agentJobHistoryInfo?.message}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="step-row">
|
||||
<td height="20">
|
||||
Duration:
|
||||
</td>
|
||||
<td height="20">
|
||||
{{agentJobHistoryInfo?.runDuration}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="step-row">
|
||||
<td height="20">
|
||||
Server:
|
||||
</td>
|
||||
<td>
|
||||
{{agentJobHistoryInfo?.server}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="step-row">
|
||||
<td height="20">
|
||||
SQL message ID:
|
||||
</td>
|
||||
<td height="20">
|
||||
{{agentJobHistoryInfo?.sqlMessageId}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="step-row">
|
||||
<td height="20">
|
||||
Retries Attempted:
|
||||
</td>
|
||||
<td height="20">
|
||||
{{agentJobHistoryInfo?.retriesAttempted}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div #jobsteps *ngIf="showSteps === true" style="flex: 1 1 auto; position: relative">
|
||||
<jobstepsview-component *ngIf="showSteps === true"></jobstepsview-component>
|
||||
</div>
|
||||
<h3 *ngIf="showSteps === false">No Steps Available</h3>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,382 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/jobHistory';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { OnInit, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy, Injectable } from '@angular/core';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/electron-browser/agentView.component';
|
||||
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
|
||||
import { RunJobAction, StopJobAction, EditJobAction, JobsRefreshAction } from 'sql/platform/jobManagement/common/jobActions';
|
||||
import { JobCacheObject } from 'sql/platform/jobManagement/common/jobManagementService';
|
||||
import { JobManagementUtilities } from 'sql/platform/jobManagement/common/jobManagementUtilities';
|
||||
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
|
||||
import {
|
||||
JobHistoryController, JobHistoryDataSource,
|
||||
JobHistoryRenderer, JobHistoryFilter, JobHistoryModel, JobHistoryRow
|
||||
} from 'sql/workbench/parts/jobManagement/electron-browser/jobHistoryTree';
|
||||
import { JobStepsViewRow } from 'sql/workbench/parts/jobManagement/electron-browser/jobStepsViewTree';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { JobManagementView } from 'sql/workbench/parts/jobManagement/electron-browser/jobManagementView';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/telemetryKeys';
|
||||
|
||||
export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
|
||||
|
||||
@Component({
|
||||
selector: DASHBOARD_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./jobHistory.component.html')),
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => JobHistoryComponent) }],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
@Injectable()
|
||||
export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
|
||||
private _tree: Tree;
|
||||
private _treeController: JobHistoryController;
|
||||
private _treeDataSource: JobHistoryDataSource;
|
||||
private _treeRenderer: JobHistoryRenderer;
|
||||
private _treeFilter: JobHistoryFilter;
|
||||
|
||||
@ViewChild('table') private _tableContainer: ElementRef;
|
||||
@ViewChild('jobsteps') private _jobStepsView: ElementRef;
|
||||
|
||||
@Input() public agentJobInfo: azdata.AgentJobInfo = undefined;
|
||||
@Input() public agentJobHistories: azdata.AgentJobHistoryInfo[] = undefined;
|
||||
public agentJobHistoryInfo: azdata.AgentJobHistoryInfo = undefined;
|
||||
|
||||
private _isVisible: boolean = false;
|
||||
private _stepRows: JobStepsViewRow[] = [];
|
||||
private _showSteps: boolean = undefined;
|
||||
private _showPreviousRuns: boolean = undefined;
|
||||
private _runStatus: string = undefined;
|
||||
private _jobCacheObject: JobCacheObject;
|
||||
private _agentJobInfo: azdata.AgentJobInfo;
|
||||
private _noJobsAvailable: boolean = false;
|
||||
|
||||
private static readonly HEADING_HEIGHT: number = 24;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
|
||||
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
|
||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||
@Inject(IDashboardService) dashboardService: IDashboardService,
|
||||
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
|
||||
) {
|
||||
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService, _agentViewComponent);
|
||||
this._treeController = new JobHistoryController();
|
||||
this._treeDataSource = new JobHistoryDataSource();
|
||||
this._treeRenderer = new JobHistoryRenderer();
|
||||
this._treeFilter = new JobHistoryFilter();
|
||||
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
|
||||
this._serverName = commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
|
||||
let jobCache = jobCacheObjectMap[this._serverName];
|
||||
if (jobCache) {
|
||||
this._jobCacheObject = jobCache;
|
||||
} else {
|
||||
this._jobCacheObject = new JobCacheObject();
|
||||
this._jobCacheObject.serverName = this._serverName;
|
||||
this._jobManagementService.addToCache(this._serverName, this._jobCacheObject);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// set base class elements
|
||||
this._visibilityElement = this._tableContainer;
|
||||
this._parentComponent = this._agentViewComponent;
|
||||
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
const self = this;
|
||||
this._treeController.onClick = (tree, element, event, origin = 'mouse') => {
|
||||
const payload = { origin: origin };
|
||||
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
|
||||
// Cancel Event
|
||||
const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown';
|
||||
if (!isMouseDown) {
|
||||
event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
|
||||
}
|
||||
event.stopPropagation();
|
||||
tree.setFocus(element, payload);
|
||||
if (element && isDoubleClick) {
|
||||
event.preventDefault(); // focus moves to editor, we need to prevent default
|
||||
} else {
|
||||
tree.setFocus(element, payload);
|
||||
tree.setSelection([element], payload);
|
||||
if (element.rowID) {
|
||||
self.setStepsTree(element);
|
||||
} else {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this._treeController.onKeyDown = (tree, event) => {
|
||||
this._treeController.onKeyDownWrapper(tree, event);
|
||||
let element = tree.getFocus();
|
||||
if (element) {
|
||||
self.setStepsTree(element);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this._tree = new Tree(this._tableContainer.nativeElement, {
|
||||
controller: this._treeController,
|
||||
dataSource: this._treeDataSource,
|
||||
filter: this._treeFilter,
|
||||
renderer: this._treeRenderer
|
||||
}, { verticalScrollMode: ScrollbarVisibility.Visible });
|
||||
this._register(attachListStyler(this._tree, this.themeService));
|
||||
this._tree.layout(dom.getContentHeight(this._tableContainer.nativeElement));
|
||||
this._telemetryService.publicLog(TelemetryKeys.JobHistoryView);
|
||||
this.initActionBar();
|
||||
}
|
||||
|
||||
private loadHistory() {
|
||||
const self = this;
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
let jobName = this._agentViewComponent.agentJobInfo.name;
|
||||
let jobId = this._agentViewComponent.jobId;
|
||||
this._jobManagementService.getJobHistory(ownerUri, jobId, jobName).then((result) => {
|
||||
if (result && result.histories) {
|
||||
self._jobCacheObject.setJobHistory(jobId, result.histories);
|
||||
self._jobCacheObject.setJobAlerts(jobId, result.alerts);
|
||||
self._jobCacheObject.setJobSchedules(jobId, result.schedules);
|
||||
self._jobCacheObject.setJobSteps(jobId, result.steps);
|
||||
this._agentViewComponent.agentJobInfo.jobSteps = this._jobCacheObject.getJobSteps(jobId);
|
||||
this._agentViewComponent.agentJobInfo.jobSchedules = this._jobCacheObject.getJobSchedules(jobId);
|
||||
this._agentViewComponent.agentJobInfo.alerts = this._jobCacheObject.getJobAlerts(jobId);
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
if (result.histories.length > 0) {
|
||||
self._showPreviousRuns = true;
|
||||
self.buildHistoryTree(self, result.histories);
|
||||
if (self._agentViewComponent.showHistory) {
|
||||
self._cd.detectChanges();
|
||||
}
|
||||
} else {
|
||||
self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, result.histories);
|
||||
self._showPreviousRuns = false;
|
||||
}
|
||||
} else {
|
||||
self._showPreviousRuns = false;
|
||||
self._showSteps = false;
|
||||
if (self._agentViewComponent.showHistory) {
|
||||
self._cd.detectChanges();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setStepsTree(element: JobHistoryRow) {
|
||||
const self = this;
|
||||
let cachedHistory = self._jobCacheObject.getJobHistory(element.jobID);
|
||||
if (cachedHistory) {
|
||||
self.agentJobHistoryInfo = cachedHistory.find(
|
||||
history => self.formatTime(history.runDate) === self.formatTime(element.runDate));
|
||||
} else {
|
||||
self.agentJobHistoryInfo = self._treeController.jobHistories.find(
|
||||
history => self.formatTime(history.runDate) === self.formatTime(element.runDate));
|
||||
}
|
||||
if (self.agentJobHistoryInfo) {
|
||||
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
|
||||
if (self.agentJobHistoryInfo.steps) {
|
||||
let jobStepStatus = this.didJobFail(self.agentJobHistoryInfo);
|
||||
self._stepRows = self.agentJobHistoryInfo.steps.map(step => {
|
||||
let stepViewRow = new JobStepsViewRow();
|
||||
stepViewRow.message = step.message;
|
||||
stepViewRow.runStatus = jobStepStatus ? JobManagementUtilities.convertToStatusString(0) :
|
||||
JobManagementUtilities.convertToStatusString(step.runStatus);
|
||||
self._runStatus = JobManagementUtilities.convertToStatusString(self.agentJobHistoryInfo.runStatus);
|
||||
stepViewRow.stepName = step.stepDetails.stepName;
|
||||
stepViewRow.stepId = step.stepDetails.id.toString();
|
||||
return stepViewRow;
|
||||
});
|
||||
self._stepRows.unshift(new JobStepsViewRow());
|
||||
self._stepRows[0].rowID = 'stepsColumn' + self._agentJobInfo.jobId;
|
||||
self._stepRows[0].stepId = nls.localize('stepRow.stepID', 'Step ID');
|
||||
self._stepRows[0].stepName = nls.localize('stepRow.stepName', 'Step Name');
|
||||
self._stepRows[0].message = nls.localize('stepRow.message', 'Message');
|
||||
this._showSteps = self._stepRows.length > 1;
|
||||
} else {
|
||||
self._showSteps = false;
|
||||
}
|
||||
if (self._agentViewComponent.showHistory) {
|
||||
self._cd.detectChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private didJobFail(job: azdata.AgentJobHistoryInfo): boolean {
|
||||
for (let i = 0; i < job.steps.length; i++) {
|
||||
if (job.steps[i].runStatus === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private buildHistoryTree(self: any, jobHistories: azdata.AgentJobHistoryInfo[]) {
|
||||
self._treeController.jobHistories = jobHistories;
|
||||
let jobHistoryRows = this._treeController.jobHistories.map(job => self.convertToJobHistoryRow(job));
|
||||
self._treeDataSource.data = jobHistoryRows;
|
||||
self._tree.setInput(new JobHistoryModel());
|
||||
self.agentJobHistoryInfo = self._treeController.jobHistories[0];
|
||||
if (self.agentJobHistoryInfo) {
|
||||
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
|
||||
}
|
||||
const payload = { origin: 'origin' };
|
||||
let element = this._treeDataSource.getFirstElement();
|
||||
this._tree.setFocus(element, payload);
|
||||
this._tree.setSelection([element], payload);
|
||||
if (element.rowID) {
|
||||
self.setStepsTree(element);
|
||||
}
|
||||
}
|
||||
|
||||
private toggleCollapse(): void {
|
||||
let arrow: HTMLElement = jQuery('.resultsViewCollapsible').get(0);
|
||||
let checkbox: any = document.getElementById('accordion');
|
||||
if (arrow.className === 'resultsViewCollapsible' && checkbox.checked === false) {
|
||||
arrow.className = 'resultsViewCollapsible collapsed';
|
||||
} else if (arrow.className === 'resultsViewCollapsible collapsed' && checkbox.checked === true) {
|
||||
arrow.className = 'resultsViewCollapsible';
|
||||
}
|
||||
}
|
||||
|
||||
private goToJobs(): void {
|
||||
this._isVisible = false;
|
||||
this._agentViewComponent.showHistory = false;
|
||||
}
|
||||
|
||||
private convertToJobHistoryRow(historyInfo: azdata.AgentJobHistoryInfo): JobHistoryRow {
|
||||
let jobHistoryRow = new JobHistoryRow();
|
||||
jobHistoryRow.runDate = this.formatTime(historyInfo.runDate);
|
||||
jobHistoryRow.runStatus = JobManagementUtilities.convertToStatusString(historyInfo.runStatus);
|
||||
jobHistoryRow.instanceID = historyInfo.instanceId;
|
||||
jobHistoryRow.jobID = historyInfo.jobId;
|
||||
return jobHistoryRow;
|
||||
}
|
||||
|
||||
private formatTime(time: string): string {
|
||||
return time.replace('T', ' ');
|
||||
}
|
||||
|
||||
private showProgressWheel(): boolean {
|
||||
return this._showPreviousRuns !== true && this._noJobsAvailable === false;
|
||||
}
|
||||
|
||||
public onFirstVisible() {
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
if (!this.agentJobInfo) {
|
||||
this.agentJobInfo = this._agentJobInfo;
|
||||
}
|
||||
|
||||
if (this.isRefreshing) {
|
||||
this.loadHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
let jobHistories = this._jobCacheObject.jobHistories[this._agentViewComponent.jobId];
|
||||
if (jobHistories && jobHistories.length > 0) {
|
||||
const self = this;
|
||||
if (this._jobCacheObject.prevJobID === this._agentViewComponent.jobId || jobHistories[0].jobId === this._agentViewComponent.jobId) {
|
||||
this._showPreviousRuns = true;
|
||||
this._agentViewComponent.agentJobInfo.jobSteps = this._jobCacheObject.getJobSteps(this._agentJobInfo.jobId);
|
||||
this._agentViewComponent.agentJobInfo.jobSchedules = this._jobCacheObject.getJobSchedules(this._agentJobInfo.jobId);
|
||||
this._agentViewComponent.agentJobInfo.alerts = this._jobCacheObject.getJobAlerts(this._agentJobInfo.jobId);
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
this.buildHistoryTree(self, jobHistories);
|
||||
this._actionBar.context = { targetObject: this._agentJobInfo, ownerUri: this.ownerUri, jobHistoryComponent: this };
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
} else if (jobHistories && jobHistories.length === 0) {
|
||||
this._showPreviousRuns = false;
|
||||
this._showSteps = false;
|
||||
this._noJobsAvailable = true;
|
||||
this._cd.detectChanges();
|
||||
} else {
|
||||
this.loadHistory();
|
||||
}
|
||||
this._jobCacheObject.prevJobID = this._agentViewComponent.jobId;
|
||||
}
|
||||
|
||||
public layout() {
|
||||
let historyDetails = jQuery('.overview-container').get(0);
|
||||
let statusBar = jQuery('.part.statusbar').get(0);
|
||||
if (historyDetails && statusBar) {
|
||||
let historyBottom = historyDetails.getBoundingClientRect().bottom;
|
||||
let statusTop = statusBar.getBoundingClientRect().top;
|
||||
|
||||
let height: number = statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT;
|
||||
|
||||
if (this._table) {
|
||||
this._table.layout(new dom.Dimension(
|
||||
dom.getContentWidth(this._tableContainer.nativeElement),
|
||||
height));
|
||||
}
|
||||
|
||||
if (this._tree) {
|
||||
this._tree.layout(dom.getContentHeight(this._tableContainer.nativeElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected initActionBar() {
|
||||
let runJobAction = this.instantiationService.createInstance(RunJobAction);
|
||||
let stopJobAction = this.instantiationService.createInstance(StopJobAction);
|
||||
let editJobAction = this.instantiationService.createInstance(EditJobAction);
|
||||
let refreshAction = this.instantiationService.createInstance(JobsRefreshAction);
|
||||
let taskbar = <HTMLElement>this.actionBarContainer.nativeElement;
|
||||
this._actionBar = new Taskbar(taskbar);
|
||||
this._actionBar.context = { targetObject: this._agentJobInfo, ownerUri: this.ownerUri, component: this };
|
||||
this._actionBar.setContent([
|
||||
{ action: runJobAction },
|
||||
{ action: stopJobAction },
|
||||
{ action: refreshAction },
|
||||
{ action: editJobAction }
|
||||
]);
|
||||
}
|
||||
|
||||
/** GETTERS */
|
||||
|
||||
public get showSteps(): boolean {
|
||||
return this._showSteps;
|
||||
}
|
||||
|
||||
public get stepRows() {
|
||||
return this._stepRows;
|
||||
}
|
||||
|
||||
public get ownerUri(): string {
|
||||
return this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
}
|
||||
|
||||
public get serverName(): string {
|
||||
return this._serverName;
|
||||
}
|
||||
|
||||
/** SETTERS */
|
||||
|
||||
public set showSteps(value: boolean) {
|
||||
this._showSteps = value;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as tree from 'vs/base/parts/tree/browser/tree';
|
||||
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { AgentJobHistoryInfo } from 'azdata';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export class JobHistoryRow {
|
||||
runDate: string;
|
||||
runStatus: string;
|
||||
instanceID: number;
|
||||
rowID: string = generateUuid();
|
||||
jobID: string;
|
||||
}
|
||||
|
||||
// Empty class just for tree input
|
||||
export class JobHistoryModel {
|
||||
public static readonly id = generateUuid();
|
||||
}
|
||||
|
||||
export class JobHistoryController extends TreeDefaults.DefaultController {
|
||||
private _jobHistories: AgentJobHistoryInfo[];
|
||||
|
||||
protected onLeftClick(tree: tree.ITree, element: JobHistoryRow, event: IMouseEvent, origin: string = 'mouse'): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public set jobHistories(value: AgentJobHistoryInfo[]) {
|
||||
this._jobHistories = value;
|
||||
}
|
||||
|
||||
public get jobHistories(): AgentJobHistoryInfo[] {
|
||||
return this._jobHistories;
|
||||
}
|
||||
|
||||
public onKeyDownWrapper(tree: tree.ITree, event: IKeyboardEvent): boolean {
|
||||
if (event.code === 'ArrowDown' || event.keyCode === 40) {
|
||||
super.onDown(tree, event);
|
||||
return super.onEnter(tree, event);
|
||||
} else if (event.code === 'ArrowUp' || event.keyCode === 38) {
|
||||
super.onUp(tree, event);
|
||||
return super.onEnter(tree, event);
|
||||
} else if (event.code !== 'Tab' && event.keyCode !== 2) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class JobHistoryDataSource implements tree.IDataSource {
|
||||
private _data: JobHistoryRow[];
|
||||
|
||||
public getId(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): string {
|
||||
if (element instanceof JobHistoryModel) {
|
||||
return JobHistoryModel.id;
|
||||
} else {
|
||||
return (element as JobHistoryRow).rowID;
|
||||
}
|
||||
}
|
||||
|
||||
public hasChildren(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): boolean {
|
||||
if (element instanceof JobHistoryModel) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public getChildren(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): Promise<JobHistoryRow[]> {
|
||||
if (element instanceof JobHistoryModel) {
|
||||
return Promise.resolve(this._data);
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public getParent(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): Promise<JobHistoryModel> {
|
||||
if (element instanceof JobHistoryModel) {
|
||||
return Promise.resolve(undefined);
|
||||
} else {
|
||||
return Promise.resolve(new JobHistoryModel());
|
||||
}
|
||||
}
|
||||
|
||||
public set data(data: JobHistoryRow[]) {
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
public getFirstElement() {
|
||||
return this._data[0];
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListTemplate {
|
||||
statusIcon: HTMLElement;
|
||||
label: HTMLElement;
|
||||
}
|
||||
|
||||
export class JobHistoryRenderer implements tree.IRenderer {
|
||||
|
||||
public getHeight(tree: tree.ITree, element: JobHistoryRow): number {
|
||||
return 30;
|
||||
}
|
||||
|
||||
public getTemplateId(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): string {
|
||||
if (element instanceof JobHistoryModel) {
|
||||
return 'jobHistoryModel';
|
||||
} else {
|
||||
return 'jobHistoryInfo';
|
||||
}
|
||||
}
|
||||
|
||||
public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): IListTemplate {
|
||||
let row = DOM.$('.list-row');
|
||||
let label = DOM.$('.label');
|
||||
let statusIcon = this.createStatusIcon();
|
||||
row.appendChild(statusIcon);
|
||||
row.appendChild(label);
|
||||
container.appendChild(row);
|
||||
return { statusIcon, label };
|
||||
}
|
||||
|
||||
public renderElement(tree: tree.ITree, element: JobHistoryRow, templateId: string, templateData: IListTemplate): void {
|
||||
templateData.label.innerHTML = element.runDate + ' ' + element.runStatus;
|
||||
let statusClass: string;
|
||||
if (element.runStatus === 'Succeeded') {
|
||||
statusClass = 'status-icon job-passed';
|
||||
} else if (element.runStatus === 'Failed') {
|
||||
statusClass = 'status-icon job-failed';
|
||||
} else {
|
||||
statusClass = 'status-icon job-unknown';
|
||||
}
|
||||
templateData.statusIcon.className = statusClass;
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: tree.ITree, templateId: string, templateData: IListTemplate): void {
|
||||
// no op
|
||||
}
|
||||
|
||||
private createStatusIcon(): HTMLElement {
|
||||
let statusIcon: HTMLElement = DOM.$('div');
|
||||
return statusIcon;
|
||||
}
|
||||
}
|
||||
|
||||
export class JobHistoryFilter implements tree.IFilter {
|
||||
private _filterString: string;
|
||||
|
||||
public isVisible(tree: tree.ITree, element: JobHistoryRow): boolean {
|
||||
return this._isJobVisible();
|
||||
}
|
||||
|
||||
private _isJobVisible(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public set filterString(val: string) {
|
||||
this._filterString = val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { ElementRef, AfterContentChecked, ViewChild } from '@angular/core';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/electron-browser/agentView.component';
|
||||
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { JobsRefreshAction, IJobActionInfo } from 'sql/platform/jobManagement/common/jobActions';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
|
||||
export abstract class JobManagementView extends TabChild implements AfterContentChecked {
|
||||
protected isVisible: boolean = false;
|
||||
protected isInitialized: boolean = false;
|
||||
protected isRefreshing: boolean = false;
|
||||
protected _showProgressWheel: boolean;
|
||||
protected _visibilityElement: ElementRef;
|
||||
protected _parentComponent: AgentViewComponent;
|
||||
protected _table: Table<any>;
|
||||
protected _actionBar: Taskbar;
|
||||
protected _serverName: string;
|
||||
public contextAction: any;
|
||||
|
||||
@ViewChild('actionbarContainer') protected actionBarContainer: ElementRef;
|
||||
|
||||
constructor(
|
||||
protected _commonService: CommonServiceInterface,
|
||||
protected _dashboardService: IDashboardService,
|
||||
protected _contextMenuService: IContextMenuService,
|
||||
protected _keybindingService: IKeybindingService,
|
||||
protected _instantiationService: IInstantiationService,
|
||||
protected _agentViewComponent: AgentViewComponent) {
|
||||
super();
|
||||
|
||||
let self = this;
|
||||
this._serverName = this._commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
|
||||
this._dashboardService.onLayout((d) => {
|
||||
self.layout();
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
if (this._visibilityElement && this._parentComponent) {
|
||||
if (this.isVisible === false && this._visibilityElement.nativeElement.offsetParent !== null) {
|
||||
this.isVisible = true;
|
||||
if (!this.isInitialized) {
|
||||
this._showProgressWheel = true;
|
||||
this.onFirstVisible();
|
||||
this.layout();
|
||||
this.isInitialized = true;
|
||||
}
|
||||
} else if (this.isVisible === true && this._parentComponent.refresh === true) {
|
||||
this._showProgressWheel = true;
|
||||
this.isRefreshing = true;
|
||||
this.onFirstVisible();
|
||||
this.layout();
|
||||
this._parentComponent.refresh = false;
|
||||
} else if (this.isVisible === true && this._visibilityElement.nativeElement.offsetParent === null) {
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract onFirstVisible();
|
||||
|
||||
protected openContextMenu(event): void {
|
||||
let rowIndex = event.cell.row;
|
||||
|
||||
let targetObject = this.getCurrentTableObject(rowIndex);
|
||||
let actions = this.getTableActions(targetObject);
|
||||
if (actions) {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
let actionContext = {
|
||||
ownerUri: ownerUri,
|
||||
targetObject: targetObject
|
||||
|
||||
};
|
||||
|
||||
let anchor = { x: event.pageX + 1, y: event.pageY };
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => actions,
|
||||
getKeyBinding: (action) => this._keybindingFor(action),
|
||||
getActionsContext: () => (actionContext)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected _keybindingFor(action: IAction): ResolvedKeybinding {
|
||||
let [kb] = this._keybindingService.lookupKeybindings(action.id);
|
||||
return kb;
|
||||
}
|
||||
|
||||
protected getTableActions(targetObject?: any): IAction[] {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected getCurrentTableObject(rowIndex: number): JobActionContext {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected initActionBar() {
|
||||
let refreshAction = this._instantiationService.createInstance(JobsRefreshAction);
|
||||
let newAction: Action = this._instantiationService.createInstance(this.contextAction);
|
||||
let taskbar = <HTMLElement>this.actionBarContainer.nativeElement;
|
||||
this._actionBar = new Taskbar(taskbar);
|
||||
this._actionBar.setContent([
|
||||
{ action: refreshAction },
|
||||
{ action: newAction }
|
||||
]);
|
||||
let context: IJobActionInfo = { component: this };
|
||||
this._actionBar.context = context;
|
||||
}
|
||||
|
||||
public refreshJobs() {
|
||||
this._agentViewComponent.refresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface JobActionContext {
|
||||
canEdit: boolean;
|
||||
job: azdata.AgentJobInfo;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
|
||||
<div class="steps-header">
|
||||
<div class="steps-icon"></div>
|
||||
<h1 style="display: inline">Steps</h1>
|
||||
</div>
|
||||
<div class='steps-tree' style="flex: 1 1 auto; position: relative">
|
||||
<div #table style="position: absolute; height: 100%; width: 100%" ></div>
|
||||
</div>
|
||||
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/jobStepsView';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
|
||||
import {
|
||||
JobStepsViewController, JobStepsViewDataSource, JobStepsViewFilter,
|
||||
JobStepsViewRenderer, JobStepsViewModel
|
||||
} from 'sql/workbench/parts/jobManagement/electron-browser/jobStepsViewTree';
|
||||
import { JobHistoryComponent } from 'sql/workbench/parts/jobManagement/electron-browser/jobHistory.component';
|
||||
import { JobManagementView } from 'sql/workbench/parts/jobManagement/electron-browser/jobManagementView';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/telemetryKeys';
|
||||
|
||||
export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component';
|
||||
|
||||
@Component({
|
||||
selector: JOBSTEPSVIEW_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./jobStepsView.component.html')),
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => JobStepsViewComponent) }],
|
||||
})
|
||||
export class JobStepsViewComponent extends JobManagementView implements OnInit, AfterContentChecked {
|
||||
|
||||
private _tree: Tree;
|
||||
private _treeController = new JobStepsViewController();
|
||||
private _treeDataSource = new JobStepsViewDataSource();
|
||||
private _treeRenderer = new JobStepsViewRenderer();
|
||||
private _treeFilter = new JobStepsViewFilter();
|
||||
|
||||
@ViewChild('table') private _tableContainer: ElementRef;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => JobHistoryComponent)) private _jobHistoryComponent: JobHistoryComponent,
|
||||
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
|
||||
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||
@Inject(IDashboardService) dashboardService: IDashboardService,
|
||||
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
|
||||
) {
|
||||
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService, undefined);
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
jQuery('.steps-tree .step-column-heading').closest('.monaco-tree-row').addClass('step-column-row');
|
||||
this.layout();
|
||||
this._tree.onDidScroll(() => {
|
||||
jQuery('.steps-tree .step-column-heading').closest('.monaco-tree-row').addClass('step-column-row');
|
||||
});
|
||||
this._treeController.onClick = (tree, element, event, origin = 'mouse') => {
|
||||
const payload = { origin: origin };
|
||||
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
|
||||
// Cancel Event
|
||||
const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown';
|
||||
if (!isMouseDown) {
|
||||
event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
|
||||
}
|
||||
event.stopPropagation();
|
||||
tree.setFocus(element, payload);
|
||||
if (element && isDoubleClick) {
|
||||
event.preventDefault(); // focus moves to editor, we need to prevent default
|
||||
} else {
|
||||
tree.setFocus(element, payload);
|
||||
tree.setSelection([element], payload);
|
||||
}
|
||||
jQuery('.steps-tree .step-column-heading').closest('.monaco-tree-row').addClass('step-column-row');
|
||||
return true;
|
||||
};
|
||||
this._treeController.onKeyDown = (tree, event) => {
|
||||
this._treeController.onKeyDownWrapper(tree, event);
|
||||
jQuery('.steps-tree .step-column-heading').closest('.monaco-tree-row').addClass('step-column-row');
|
||||
return true;
|
||||
};
|
||||
this._tree.onDidFocus(() => {
|
||||
this._tree.focusNth(1);
|
||||
let element = this._tree.getFocus();
|
||||
this._tree.select(element);
|
||||
});
|
||||
this._tree.setInput(new JobStepsViewModel());
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._treeDataSource.data = this._jobHistoryComponent.stepRows;
|
||||
this._tree = new Tree(this._tableContainer.nativeElement, {
|
||||
controller: this._treeController,
|
||||
dataSource: this._treeDataSource,
|
||||
filter: this._treeFilter,
|
||||
renderer: this._treeRenderer
|
||||
}, { verticalScrollMode: ScrollbarVisibility.Visible, horizontalScrollMode: ScrollbarVisibility.Visible });
|
||||
this._register(attachListStyler(this._tree, this.themeService));
|
||||
this._telemetryService.publicLog(TelemetryKeys.JobStepsView);
|
||||
}
|
||||
|
||||
public onFirstVisible() {
|
||||
}
|
||||
|
||||
public layout() {
|
||||
if (this._tree) {
|
||||
let treeheight = dom.getContentHeight(this._tableContainer.nativeElement);
|
||||
this._tree.layout(treeheight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as tree from 'vs/base/parts/tree/browser/tree';
|
||||
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export class JobStepsViewRow {
|
||||
public stepId: string;
|
||||
public stepName: string;
|
||||
public message: string;
|
||||
public rowID: string = generateUuid();
|
||||
public runStatus: string;
|
||||
}
|
||||
|
||||
// Empty class just for tree input
|
||||
export class JobStepsViewModel {
|
||||
public static readonly id = generateUuid();
|
||||
}
|
||||
|
||||
export class JobStepsViewController extends TreeDefaults.DefaultController {
|
||||
|
||||
protected onLeftClick(tree: tree.ITree, element: JobStepsViewRow, event: IMouseEvent, origin: string = 'mouse'): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public onContextMenu(tree: tree.ITree, element: JobStepsViewRow, event: tree.ContextMenuEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public onKeyDownWrapper(tree: tree.ITree, event: IKeyboardEvent): boolean {
|
||||
if (event.code === 'ArrowDown' || event.keyCode === 40) {
|
||||
super.onDown(tree, event);
|
||||
return super.onEnter(tree, event);
|
||||
} else if (event.code === 'ArrowUp' || event.keyCode === 38) {
|
||||
super.onUp(tree, event);
|
||||
return super.onEnter(tree, event);
|
||||
} else if (event.code !== 'Tab' && event.keyCode !== 2) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class JobStepsViewDataSource implements tree.IDataSource {
|
||||
private _data: JobStepsViewRow[];
|
||||
|
||||
public getId(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): string {
|
||||
if (element instanceof JobStepsViewModel) {
|
||||
return JobStepsViewModel.id;
|
||||
} else {
|
||||
return (element as JobStepsViewRow).rowID;
|
||||
}
|
||||
}
|
||||
|
||||
public hasChildren(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): boolean {
|
||||
if (element instanceof JobStepsViewModel) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public getChildren(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): Promise<JobStepsViewRow[]> {
|
||||
if (element instanceof JobStepsViewModel) {
|
||||
return Promise.resolve(this._data);
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public getParent(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): Promise<JobStepsViewModel> {
|
||||
if (element instanceof JobStepsViewModel) {
|
||||
return Promise.resolve(undefined);
|
||||
} else {
|
||||
return Promise.resolve(new JobStepsViewModel());
|
||||
}
|
||||
}
|
||||
|
||||
public set data(data: JobStepsViewRow[]) {
|
||||
this._data = data;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListTemplate {
|
||||
statusIcon: HTMLElement;
|
||||
label: HTMLElement;
|
||||
}
|
||||
|
||||
export class JobStepsViewRenderer implements tree.IRenderer {
|
||||
|
||||
public getHeight(tree: tree.ITree, element: JobStepsViewRow): number {
|
||||
return 40;
|
||||
}
|
||||
|
||||
public getTemplateId(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): string {
|
||||
if (element instanceof JobStepsViewModel) {
|
||||
return 'jobStepsViewModel';
|
||||
} else {
|
||||
return 'jobStepsViewRow';
|
||||
}
|
||||
}
|
||||
|
||||
public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): IListTemplate {
|
||||
let row = DOM.$('.list-row');
|
||||
let label = DOM.$('.label');
|
||||
let statusIcon = this.createStatusIcon();
|
||||
row.appendChild(statusIcon);
|
||||
row.appendChild(label);
|
||||
container.appendChild(row);
|
||||
return { statusIcon, label };
|
||||
}
|
||||
|
||||
public renderElement(tree: tree.ITree, element: JobStepsViewRow, templateId: string, templateData: IListTemplate): void {
|
||||
let stepIdCol: HTMLElement = DOM.$('div');
|
||||
stepIdCol.className = 'tree-id-col';
|
||||
stepIdCol.innerText = element.stepId;
|
||||
let stepNameCol: HTMLElement = DOM.$('div');
|
||||
stepNameCol.className = 'tree-name-col';
|
||||
stepNameCol.innerText = element.stepName;
|
||||
let stepMessageCol: HTMLElement = DOM.$('div');
|
||||
stepMessageCol.className = 'tree-message-col';
|
||||
stepMessageCol.innerText = element.message;
|
||||
if (element.rowID.includes('stepsColumn')) {
|
||||
stepNameCol.className += ' step-column-heading';
|
||||
stepIdCol.className += ' step-column-heading';
|
||||
stepMessageCol.className += ' step-column-heading';
|
||||
}
|
||||
DOM.clearNode(templateData.label);
|
||||
templateData.label.appendChild(stepIdCol);
|
||||
templateData.label.appendChild(stepNameCol);
|
||||
templateData.label.appendChild(stepMessageCol);
|
||||
if (element.runStatus) {
|
||||
if (element.runStatus === 'Succeeded') {
|
||||
templateData.statusIcon.className = 'status-icon step-passed';
|
||||
} else if (element.runStatus === 'Failed') {
|
||||
templateData.statusIcon.className = 'status-icon step-failed';
|
||||
} else {
|
||||
templateData.statusIcon.className = 'status-icon step-unknown';
|
||||
}
|
||||
} else {
|
||||
templateData.statusIcon.className = '';
|
||||
}
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: tree.ITree, templateId: string, templateData: IListTemplate): void {
|
||||
// no op
|
||||
}
|
||||
|
||||
private createStatusIcon(): HTMLElement {
|
||||
let statusIcon: HTMLElement = DOM.$('div');
|
||||
return statusIcon;
|
||||
}
|
||||
}
|
||||
|
||||
export class JobStepsViewFilter implements tree.IFilter {
|
||||
private _filterString: string;
|
||||
|
||||
public isVisible(tree: tree.ITree, element: JobStepsViewRow): boolean {
|
||||
return this._isJobVisible();
|
||||
}
|
||||
|
||||
private _isJobVisible(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public set filterString(val: string) {
|
||||
this._filterString = val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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">Jobs</h1>
|
||||
<h1 class="job-heading" *ngIf="_isCloud === true">No Jobs Available</h1>
|
||||
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
|
||||
</div>
|
||||
|
||||
<div #actionbarContainer class="agent-actionbar-container"></div>
|
||||
|
||||
<div #jobsgrid class="jobview-grid"></div>
|
||||
@@ -0,0 +1,948 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/jobs';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/electron-browser/agentView.component';
|
||||
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowDetailView';
|
||||
import { JobCacheObject } from 'sql/platform/jobManagement/common/jobManagementService';
|
||||
import { EditJobAction, DeleteJobAction, NewJobAction } from 'sql/platform/jobManagement/common/jobActions';
|
||||
import { JobManagementUtilities } from 'sql/platform/jobManagement/common/jobManagementUtilities';
|
||||
import { HeaderFilter } from 'sql/base/browser/ui/table/plugins/headerFilter.plugin';
|
||||
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
|
||||
import { JobManagementView, JobActionContext } from 'sql/workbench/parts/jobManagement/electron-browser/jobManagementView';
|
||||
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { tableBackground, cellBackground, cellBorderColor } from 'sql/platform/theme/common/colors';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/telemetryKeys';
|
||||
|
||||
export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
|
||||
export const ROW_HEIGHT: number = 45;
|
||||
export const ACTIONBAR_PADDING: number = 10;
|
||||
|
||||
@Component({
|
||||
selector: JOBSVIEW_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./jobsView.component.html')),
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => JobsViewComponent) }],
|
||||
})
|
||||
|
||||
export class JobsViewComponent extends JobManagementView implements OnInit, OnDestroy {
|
||||
|
||||
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: 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'
|
||||
}
|
||||
];
|
||||
|
||||
private _jobCacheObject: JobCacheObject;
|
||||
private rowDetail: RowDetailView;
|
||||
private filterPlugin: any;
|
||||
private dataView: any;
|
||||
private _isCloud: boolean;
|
||||
private filterStylingMap: { [columnName: string]: [any]; } = {};
|
||||
private filterStack = ['start'];
|
||||
private filterValueMap: { [columnName: string]: string[]; } = {};
|
||||
private sortingStylingMap: { [columnName: string]: any; } = {};
|
||||
|
||||
public jobs: azdata.AgentJobInfo[];
|
||||
private jobHistories: { [jobId: string]: azdata.AgentJobHistoryInfo[]; } = Object.create(null);
|
||||
private jobSteps: { [jobId: string]: azdata.AgentJobStepInfo[]; } = Object.create(null);
|
||||
private jobAlerts: { [jobId: string]: azdata.AgentAlertInfo[]; } = Object.create(null);
|
||||
private jobSchedules: { [jobId: string]: azdata.AgentJobScheduleInfo[]; } = Object.create(null);
|
||||
public contextAction = NewJobAction;
|
||||
|
||||
@ViewChild('jobsgrid') _gridEl: ElementRef;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(IWorkbenchThemeService) private _themeService: IWorkbenchThemeService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService,
|
||||
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||
@Inject(IDashboardService) _dashboardService: IDashboardService,
|
||||
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
|
||||
) {
|
||||
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService, _agentViewComponent);
|
||||
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
|
||||
let jobCache = jobCacheObjectMap[this._serverName];
|
||||
if (jobCache) {
|
||||
this._jobCacheObject = jobCache;
|
||||
} else {
|
||||
this._jobCacheObject = new JobCacheObject();
|
||||
this._jobCacheObject.serverName = this._serverName;
|
||||
this._jobManagementService.addToCache(this._serverName, this._jobCacheObject);
|
||||
}
|
||||
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// set base class elements
|
||||
this._visibilityElement = this._gridEl;
|
||||
this._parentComponent = this._agentViewComponent;
|
||||
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
|
||||
this._telemetryService.publicLog(TelemetryKeys.JobsView);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
}
|
||||
|
||||
public layout() {
|
||||
let jobsViewToolbar = jQuery('jobsview-component .agent-actionbar-container').get(0);
|
||||
let statusBar = jQuery('.part.statusbar').get(0);
|
||||
if (jobsViewToolbar && statusBar) {
|
||||
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom + ACTIONBAR_PADDING;
|
||||
let statusTop = statusBar.getBoundingClientRect().top;
|
||||
this._table.layout(new dom.Dimension(
|
||||
dom.getContentWidth(this._gridEl.nativeElement),
|
||||
statusTop - toolbarBottom));
|
||||
}
|
||||
}
|
||||
|
||||
onFirstVisible() {
|
||||
let self = this;
|
||||
let cached: boolean = false;
|
||||
if (this._jobCacheObject.serverName === this._serverName && this._jobCacheObject.jobs.length > 0) {
|
||||
cached = true;
|
||||
this.jobs = this._jobCacheObject.jobs;
|
||||
}
|
||||
|
||||
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: false
|
||||
};
|
||||
|
||||
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
|
||||
|
||||
let rowDetail = new RowDetailView({
|
||||
cssClass: '_detail_selector',
|
||||
process: (job) => {
|
||||
(<any>rowDetail).onAsyncResponse.notify({
|
||||
'itemDetail': job
|
||||
}, undefined, this);
|
||||
},
|
||||
useRowClick: false,
|
||||
panelRows: 1
|
||||
});
|
||||
this.rowDetail = rowDetail;
|
||||
columns.unshift(this.rowDetail.getColumnDefinition());
|
||||
let filterPlugin = new HeaderFilter({}, this._themeService);
|
||||
this.filterPlugin = filterPlugin;
|
||||
jQuery(this._gridEl.nativeElement).empty();
|
||||
jQuery(this.actionBarContainer.nativeElement).empty();
|
||||
this.initActionBar();
|
||||
this._table = new Table(this._gridEl.nativeElement, { columns }, options);
|
||||
this._table.grid.setData(this.dataView, true);
|
||||
this._table.grid.onClick.subscribe((e, args) => {
|
||||
let job = self.getJob(args);
|
||||
self._agentViewComponent.jobId = job.jobId;
|
||||
self._agentViewComponent.agentJobInfo = job;
|
||||
self._agentViewComponent.showHistory = true;
|
||||
});
|
||||
this._register(this._table.onContextMenu(e => {
|
||||
self.openContextMenu(e);
|
||||
}));
|
||||
|
||||
if (cached && this._agentViewComponent.refresh !== true) {
|
||||
this.onJobsAvailable(null);
|
||||
this._showProgressWheel = false;
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
} else {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._jobManagementService.getJobs(ownerUri).then((result) => {
|
||||
if (result && result.jobs) {
|
||||
self.jobs = result.jobs;
|
||||
self._jobCacheObject.jobs = self.jobs;
|
||||
self.onJobsAvailable(result.jobs);
|
||||
} else {
|
||||
// TODO: handle error
|
||||
}
|
||||
|
||||
this._showProgressWheel = false;
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onJobsAvailable(jobs: azdata.AgentJobInfo[]) {
|
||||
let jobViews: any;
|
||||
let start: boolean = true;
|
||||
if (!jobs) {
|
||||
let dataView = this._jobCacheObject.dataView;
|
||||
jobViews = dataView.getItems();
|
||||
start = false;
|
||||
} else {
|
||||
jobViews = jobs.map((job) => {
|
||||
return {
|
||||
id: job.jobId,
|
||||
jobId: job.jobId,
|
||||
name: job.name,
|
||||
lastRun: JobManagementUtilities.convertToLastRun(job.lastRun),
|
||||
nextRun: JobManagementUtilities.convertToNextRun(job.nextRun),
|
||||
enabled: JobManagementUtilities.convertToResponse(job.enabled),
|
||||
currentExecutionStatus: JobManagementUtilities.convertToExecutionStatusString(job.currentExecutionStatus),
|
||||
category: job.category,
|
||||
runnable: JobManagementUtilities.convertToResponse(job.runnable),
|
||||
hasSchedule: JobManagementUtilities.convertToResponse(job.hasSchedule),
|
||||
lastRunOutcome: JobManagementUtilities.convertToStatusString(job.lastRunOutcome)
|
||||
};
|
||||
});
|
||||
}
|
||||
this._table.registerPlugin(<any>this.rowDetail);
|
||||
this.filterPlugin.onFilterApplied.subscribe((e, args) => {
|
||||
this.dataView.refresh();
|
||||
this._table.grid.resetActiveCell();
|
||||
let filterValues = args.column.filterValues;
|
||||
if (filterValues) {
|
||||
if (filterValues.length === 0) {
|
||||
// if an associated styling exists with the current filters
|
||||
if (this.filterStylingMap[args.column.name]) {
|
||||
let filterLength = this.filterStylingMap[args.column.name].length;
|
||||
// then remove the filtered styling
|
||||
for (let i = 0; i < filterLength; i++) {
|
||||
let lastAppliedStyle = this.filterStylingMap[args.column.name].pop();
|
||||
this._table.grid.removeCellCssStyles(lastAppliedStyle[0]);
|
||||
}
|
||||
delete this.filterStylingMap[args.column.name];
|
||||
let index = this.filterStack.indexOf(args.column.name, 0);
|
||||
if (index > -1) {
|
||||
this.filterStack.splice(index, 1);
|
||||
delete this.filterValueMap[args.column.name];
|
||||
}
|
||||
// apply the previous filter styling
|
||||
let currentItems = this.dataView.getFilteredItems();
|
||||
let styledItems = this.filterValueMap[this.filterStack[this.filterStack.length - 1]][1];
|
||||
if (styledItems === currentItems) {
|
||||
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]);
|
||||
}
|
||||
} else {
|
||||
// style it all over again
|
||||
let seenJobs = 0;
|
||||
for (let i = 0; i < currentItems.length; i++) {
|
||||
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);
|
||||
if (this.filterStack.indexOf(args.column.name) < 0) {
|
||||
this.filterStack.push(args.column.name);
|
||||
this.filterValueMap[args.column.name] = [filterValues];
|
||||
}
|
||||
// one expansion for the row and one for
|
||||
// the error detail
|
||||
seenJobs++;
|
||||
i++;
|
||||
}
|
||||
seenJobs++;
|
||||
}
|
||||
this.dataView.refresh();
|
||||
this.filterValueMap[args.column.name].push(this.dataView.getFilteredItems());
|
||||
this._table.grid.resetActiveCell();
|
||||
}
|
||||
if (this.filterStack.length === 0) {
|
||||
this.filterStack = ['start'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let seenJobs = 0;
|
||||
for (let i = 0; i < this.jobs.length; i++) {
|
||||
this._table.grid.removeCellCssStyles('error-row' + i.toString());
|
||||
let item = this.dataView.getItemByIdx(i);
|
||||
// current filter
|
||||
if (_.contains(filterValues, item[args.column.field])) {
|
||||
// check all previous filters
|
||||
if (this.checkPreviousFilters(item)) {
|
||||
if (item.lastRunOutcome === 'Failed') {
|
||||
this.addToStyleHash(seenJobs, false, this.filterStylingMap, args.column.name);
|
||||
if (this.filterStack.indexOf(args.column.name) < 0) {
|
||||
this.filterStack.push(args.column.name);
|
||||
this.filterValueMap[args.column.name] = [filterValues];
|
||||
}
|
||||
// one expansion for the row and one for
|
||||
// the error detail
|
||||
seenJobs++;
|
||||
i++;
|
||||
}
|
||||
seenJobs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dataView.refresh();
|
||||
if (this.filterValueMap[args.column.name]) {
|
||||
this.filterValueMap[args.column.name].push(this.dataView.getFilteredItems());
|
||||
} else {
|
||||
this.filterValueMap[args.column.name] = this.dataView.getFilteredItems();
|
||||
}
|
||||
|
||||
this._table.grid.resetActiveCell();
|
||||
}
|
||||
} else {
|
||||
this.expandJobs(false);
|
||||
}
|
||||
});
|
||||
|
||||
this.filterPlugin.onCommand.subscribe((e, args: any) => {
|
||||
this.columnSort(args.column.name, args.command === 'sort-asc');
|
||||
});
|
||||
this._table.registerPlugin(<HeaderFilter>this.filterPlugin);
|
||||
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(jobViews);
|
||||
this.dataView.setFilter((item) => this.filter(item));
|
||||
this.dataView.endUpdate();
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
|
||||
this.expandJobs(start);
|
||||
// tooltip for job name
|
||||
jQuery('.jobview-jobnamerow').hover(e => {
|
||||
let currentTarget = e.currentTarget;
|
||||
currentTarget.title = currentTarget.innerText;
|
||||
});
|
||||
|
||||
const self = this;
|
||||
this._table.grid.onColumnsResized.subscribe((e, data: any) => {
|
||||
let nameWidth: number = data.grid.getColumns()[1].width;
|
||||
// adjust job name when resized
|
||||
jQuery('#jobsDiv .jobview-grid .slick-cell.l1.r1 .jobview-jobnametext').css('width', `${nameWidth - 10}px`);
|
||||
// adjust error message when resized
|
||||
jQuery('#jobsDiv .jobview-grid .slick-cell.l1.r1.error-row .jobview-jobnametext').css('width', '100%');
|
||||
|
||||
// generate job charts again
|
||||
self.jobs.forEach(job => {
|
||||
let jobHistories = self._jobCacheObject.getJobHistory(job.jobId);
|
||||
if (jobHistories) {
|
||||
let previousRuns = jobHistories.slice(jobHistories.length - 5, jobHistories.length);
|
||||
self.createJobChart(job.jobId, previousRuns);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
jQuery('#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) => {
|
||||
jQuery('#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;
|
||||
this.filterValueMap['start'] = [[], this.dataView.getItems()];
|
||||
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 = jQuery(e.currentTarget);
|
||||
let targetChildren = jQuery(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(jQuery(sibling).css('top'), 10);
|
||||
if (siblingTop === top + ROW_HEIGHT) {
|
||||
jQuery(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 = jQuery(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(jQuery(sibling).css('top'), 10);
|
||||
if (siblingTop === top + ROW_HEIGHT) {
|
||||
jQuery(sibling.children).removeClass('hovered');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setRowWithErrorClass(hash: { [index: number]: { [id: string]: string; } }, row: number, errorClass: string) {
|
||||
hash[row] = {
|
||||
'_detail_selector': errorClass,
|
||||
'id': errorClass,
|
||||
'jobId': errorClass,
|
||||
'name': errorClass,
|
||||
'lastRun': errorClass,
|
||||
'nextRun': errorClass,
|
||||
'enabled': errorClass,
|
||||
'currentExecutionStatus': errorClass,
|
||||
'category': errorClass,
|
||||
'runnable': errorClass,
|
||||
'hasSchedule': errorClass,
|
||||
'lastRunOutcome': errorClass,
|
||||
'previousRuns': errorClass
|
||||
};
|
||||
return hash;
|
||||
}
|
||||
|
||||
private addToStyleHash(row: number, start: boolean, map: any, columnName: string) {
|
||||
let hash: {
|
||||
[index: number]: {
|
||||
[id: string]: string;
|
||||
}
|
||||
} = {};
|
||||
hash = this.setRowWithErrorClass(hash, row, 'job-with-error');
|
||||
hash = this.setRowWithErrorClass(hash, row + 1, 'error-row');
|
||||
if (start) {
|
||||
if (map['start']) {
|
||||
map['start'].push(['error-row' + row.toString(), hash]);
|
||||
} else {
|
||||
map['start'] = [['error-row' + row.toString(), hash]];
|
||||
}
|
||||
} else {
|
||||
if (map[columnName]) {
|
||||
map[columnName].push(['error-row' + row.toString(), hash]);
|
||||
} else {
|
||||
map[columnName] = [['error-row' + row.toString(), hash]];
|
||||
}
|
||||
}
|
||||
this._table.grid.setCellCssStyles('error-row' + row.toString(), hash);
|
||||
}
|
||||
|
||||
private renderName(row, cell, value, columnDef, dataContext) {
|
||||
let resultIndicatorClass: string;
|
||||
switch (dataContext.lastRunOutcome) {
|
||||
case ('Succeeded'):
|
||||
resultIndicatorClass = 'jobview-jobnameindicatorsuccess';
|
||||
break;
|
||||
case ('Failed'):
|
||||
resultIndicatorClass = 'jobview-jobnameindicatorfailure';
|
||||
break;
|
||||
case ('Cancelled'):
|
||||
resultIndicatorClass = 'jobview-jobnameindicatorcancel';
|
||||
break;
|
||||
case ('Status Unknown'):
|
||||
resultIndicatorClass = 'jobview-jobnameindicatorunknown';
|
||||
break;
|
||||
default:
|
||||
resultIndicatorClass = 'jobview-jobnameindicatorfailure';
|
||||
break;
|
||||
}
|
||||
|
||||
return '<table class="jobview-jobnametable"><tr class="jobview-jobnamerow">' +
|
||||
'<td nowrap class=' + resultIndicatorClass + '></td>' +
|
||||
'<td nowrap class="jobview-jobnametext">' + escape(dataContext.name) + '</td>' +
|
||||
'</tr></table>';
|
||||
}
|
||||
|
||||
private renderChartsPostHistory(row, cell, value, columnDef, dataContext) {
|
||||
let runChart = this._jobCacheObject.getRunChart(dataContext.id);
|
||||
if (runChart && runChart.length > 0) {
|
||||
return `<table class="jobprevruns" id="${dataContext.id}">
|
||||
<tr>
|
||||
<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>`;
|
||||
} 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 {
|
||||
let item = this.dataView.getItemByIdx(rowIdx);
|
||||
item.message = this._agentViewComponent.expanded.get(item.jobId);
|
||||
this.rowDetail.applyTemplateNewLineHeight(item, true);
|
||||
}
|
||||
|
||||
private async loadJobHistories() {
|
||||
if (this.jobs) {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
let separatedJobs = this.separateFailingJobs();
|
||||
// grab histories of the failing jobs first
|
||||
// so they can be expanded quicker
|
||||
let failing = separatedJobs[0];
|
||||
let passing = separatedJobs[1];
|
||||
Promise.all([this.curateJobHistory(failing, ownerUri), this.curateJobHistory(passing, ownerUri)]);
|
||||
}
|
||||
}
|
||||
|
||||
private separateFailingJobs(): azdata.AgentJobInfo[][] {
|
||||
let failing = [];
|
||||
let nonFailing = [];
|
||||
for (let i = 0; i < this.jobs.length; i++) {
|
||||
if (this.jobs[i].lastRunOutcome === 0) {
|
||||
failing.push(this.jobs[i]);
|
||||
} else {
|
||||
nonFailing.push(this.jobs[i]);
|
||||
}
|
||||
}
|
||||
return [failing, nonFailing];
|
||||
}
|
||||
|
||||
private checkPreviousFilters(item): boolean {
|
||||
for (let column in this.filterValueMap) {
|
||||
if (column !== 'start' && this.filterValueMap[column][0].length > 0) {
|
||||
if (!_.contains(this.filterValueMap[column][0], item[JobManagementUtilities.convertColNameToField(column)])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private isErrorRow(cell: HTMLElement) {
|
||||
return cell.classList.contains('error-row');
|
||||
}
|
||||
|
||||
private getJob(args: Slick.OnClickEventArgs<any>): azdata.AgentJobInfo {
|
||||
let row = args.row;
|
||||
let jobName: string;
|
||||
let cell = args.grid.getCellNode(row, 1);
|
||||
if (this.isErrorRow(cell)) {
|
||||
jobName = args.grid.getCellNode(row - 1, 1).innerText.trim();
|
||||
} else {
|
||||
jobName = cell.innerText.trim();
|
||||
}
|
||||
let job = this.jobs.filter(job => job.name === jobName)[0];
|
||||
return job;
|
||||
}
|
||||
|
||||
private async curateJobHistory(jobs: azdata.AgentJobInfo[], ownerUri: string) {
|
||||
const self = this;
|
||||
for (let job of jobs) {
|
||||
let result = await this._jobManagementService.getJobHistory(ownerUri, job.jobId, job.name);
|
||||
if (result) {
|
||||
self.jobSteps[job.jobId] = result.steps ? result.steps : [];
|
||||
self.jobAlerts[job.jobId] = result.alerts ? result.alerts : [];
|
||||
self.jobSchedules[job.jobId] = result.schedules ? result.schedules : [];
|
||||
self.jobHistories[job.jobId] = result.histories ? result.histories : [];
|
||||
self._jobCacheObject.setJobSteps(job.jobId, self.jobSteps[job.jobId]);
|
||||
self._jobCacheObject.setJobHistory(job.jobId, self.jobHistories[job.jobId]);
|
||||
self._jobCacheObject.setJobAlerts(job.jobId, self.jobAlerts[job.jobId]);
|
||||
self._jobCacheObject.setJobSchedules(job.jobId, self.jobSchedules[job.jobId]);
|
||||
let jobHistories = self._jobCacheObject.getJobHistory(job.jobId);
|
||||
let previousRuns: azdata.AgentJobHistoryInfo[];
|
||||
if (jobHistories.length >= 5) {
|
||||
previousRuns = jobHistories.slice(jobHistories.length - 5, jobHistories.length);
|
||||
} else {
|
||||
previousRuns = jobHistories;
|
||||
}
|
||||
self.createJobChart(job.jobId, previousRuns);
|
||||
if (self._agentViewComponent.expanded.has(job.jobId)) {
|
||||
let lastJobHistory = jobHistories[jobHistories.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;
|
||||
item['name'] = nls.localize('jobsView.error', 'Error: ') + errorMessage;
|
||||
self._agentViewComponent.setExpanded(job.jobId, item['name']);
|
||||
self.dataView.updateItem(job.jobId + '.error', item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createJobChart(jobId: string, jobHistories: azdata.AgentJobHistoryInfo[]): void {
|
||||
let chartHeights = this.getChartHeights(jobHistories);
|
||||
let runCharts = [];
|
||||
for (let i = 0; i < chartHeights.length; i++) {
|
||||
let runGraph = jQuery(`table.jobprevruns#${jobId} > tbody > tr > td > div.bar${i}`);
|
||||
if (runGraph.length > 0) {
|
||||
runGraph.css('height', chartHeights[i]);
|
||||
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
|
||||
runGraph.css('background', bgColor);
|
||||
runGraph.hover((e) => {
|
||||
let currentTarget = e.currentTarget;
|
||||
currentTarget.title = jobHistories[i].runDuration;
|
||||
});
|
||||
runCharts.push(runGraph.get(0).outerHTML);
|
||||
}
|
||||
}
|
||||
if (runCharts.length > 0) {
|
||||
this._jobCacheObject.setRunChart(jobId, runCharts);
|
||||
}
|
||||
}
|
||||
|
||||
// chart height normalization logic
|
||||
private getChartHeights(jobHistories: azdata.AgentJobHistoryInfo[]): string[] {
|
||||
if (!jobHistories || jobHistories.length === 0) {
|
||||
return [];
|
||||
}
|
||||
let maxDuration: number = 0;
|
||||
jobHistories.forEach(history => {
|
||||
let historyDuration = JobManagementUtilities.convertDurationToSeconds(history.runDuration);
|
||||
if (historyDuration > maxDuration) {
|
||||
maxDuration = historyDuration;
|
||||
}
|
||||
});
|
||||
maxDuration = maxDuration === 0 ? 1 : maxDuration;
|
||||
let maxBarHeight: number = 24;
|
||||
let chartHeights = [];
|
||||
let zeroDurationJobCount = 0;
|
||||
for (let i = 0; i < jobHistories.length; i++) {
|
||||
let duration = jobHistories[i].runDuration;
|
||||
let chartHeight = (maxBarHeight * JobManagementUtilities.convertDurationToSeconds(duration)) / maxDuration;
|
||||
chartHeights.push(`${chartHeight}px`);
|
||||
if (chartHeight === 0) {
|
||||
zeroDurationJobCount++;
|
||||
}
|
||||
}
|
||||
// if the durations are all 0 secs, show minimal chart
|
||||
// instead of nothing
|
||||
if (zeroDurationJobCount === jobHistories.length) {
|
||||
return Array(jobHistories.length).fill('5px');
|
||||
} else {
|
||||
return chartHeights;
|
||||
}
|
||||
}
|
||||
|
||||
private expandJobs(start: boolean): void {
|
||||
if (start) {
|
||||
this._agentViewComponent.expanded = new Map<string, string>();
|
||||
}
|
||||
let expandedJobs = this._agentViewComponent.expanded;
|
||||
let expansions = 0;
|
||||
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._agentViewComponent.setExpanded(job.jobId, 'Loading Error...');
|
||||
} else if (job.lastRunOutcome === 0 && expandedJobs.get(job.jobId)) {
|
||||
this.addToStyleHash(i + expansions, start, this.filterStylingMap, undefined);
|
||||
expansions++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private filter(item: any) {
|
||||
let columns = this._table.grid.getColumns();
|
||||
let value = true;
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
let col: any = columns[i];
|
||||
let filterValues = col.filterValues;
|
||||
if (filterValues && filterValues.length > 0) {
|
||||
if (item._parent) {
|
||||
value = value && _.contains(filterValues, item._parent[col.field]);
|
||||
} else {
|
||||
value = value && _.contains(filterValues, item[col.field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private columnSort(column: string, isAscending: boolean) {
|
||||
let items = this.dataView.getItems();
|
||||
// get error items here and remove them
|
||||
let jobItems = items.filter(x => x._parent === undefined);
|
||||
let errorItems = items.filter(x => x._parent !== undefined);
|
||||
this.sortingStylingMap[column] = items;
|
||||
switch (column) {
|
||||
case ('Name'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => {
|
||||
return item1.name.localeCompare(item2.name);
|
||||
}, isAscending);
|
||||
break;
|
||||
}
|
||||
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'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => this.dateCompare(item1, item2, false), isAscending);
|
||||
break;
|
||||
}
|
||||
case ('Enabled'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => {
|
||||
return item1.enabled.localeCompare(item2.enabled);
|
||||
}, isAscending);
|
||||
break;
|
||||
}
|
||||
case ('Status'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => {
|
||||
return item1.currentExecutionStatus.localeCompare(item2.currentExecutionStatus);
|
||||
}, isAscending);
|
||||
break;
|
||||
}
|
||||
case ('Category'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => {
|
||||
return item1.category.localeCompare(item2.category);
|
||||
}, isAscending);
|
||||
break;
|
||||
}
|
||||
case ('Runnable'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => {
|
||||
return item1.runnable.localeCompare(item2.runnable);
|
||||
}, isAscending);
|
||||
break;
|
||||
}
|
||||
case ('Schedule'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => {
|
||||
return item1.hasSchedule.localeCompare(item2.hasSchedule);
|
||||
}, isAscending);
|
||||
break;
|
||||
}
|
||||
case ('Last Run Outcome'): {
|
||||
this.dataView.setItems(jobItems);
|
||||
// sort the actual jobs
|
||||
this.dataView.sort((item1, item2) => {
|
||||
return item1.lastRunOutcome.localeCompare(item2.lastRunOutcome);
|
||||
}, isAscending);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// insert the errors back again
|
||||
let jobItemsLength = jobItems.length;
|
||||
for (let i = 0; i < jobItemsLength; i++) {
|
||||
let item = jobItems[i];
|
||||
if (item._child) {
|
||||
let child = errorItems.find(error => error === item._child);
|
||||
jobItems.splice(i + 1, 0, child);
|
||||
jobItemsLength++;
|
||||
}
|
||||
}
|
||||
this.dataView.setItems(jobItems);
|
||||
// remove old style
|
||||
if (this.filterStylingMap[column]) {
|
||||
let filterLength = this.filterStylingMap[column].length;
|
||||
for (let i = 0; i < filterLength; i++) {
|
||||
let lastAppliedStyle = this.filterStylingMap[column].pop();
|
||||
this._table.grid.removeCellCssStyles(lastAppliedStyle[0]);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < this.jobs.length; i++) {
|
||||
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++) {
|
||||
let item = items[i];
|
||||
if (item.lastRunOutcome === 'Failed') {
|
||||
this.addToStyleHash(i, false, this.sortingStylingMap, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private dateCompare(item1: any, item2: any, lastRun: boolean): number {
|
||||
let exceptionString = lastRun ? 'Never Run' : 'Not Scheduled';
|
||||
if (item2.lastRun === exceptionString && item1.lastRun !== exceptionString) {
|
||||
return -1;
|
||||
} else if (item1.lastRun === exceptionString && item2.lastRun !== exceptionString) {
|
||||
return 1;
|
||||
} else if (item1.lastRun === exceptionString && item2.lastRun === exceptionString) {
|
||||
return 0;
|
||||
} else {
|
||||
let date1 = new Date(item1.lastRun);
|
||||
let date2 = new Date(item2.lastRun);
|
||||
if (date1 > date2) {
|
||||
return 1;
|
||||
} else if (date1 === date2) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateTheme(theme: IColorTheme) {
|
||||
let bgColor = theme.getColor(tableBackground);
|
||||
let cellColor = theme.getColor(cellBackground);
|
||||
let borderColor = theme.getColor(cellBorderColor);
|
||||
let headerColumns = jQuery('#agentViewDiv .slick-header-column');
|
||||
let cells = jQuery('.grid-canvas .ui-widget-content.slick-row .slick-cell');
|
||||
let cellDetails = jQuery('#jobsDiv .dynamic-cell-detail');
|
||||
headerColumns.toArray().forEach(col => {
|
||||
col.style.background = bgColor.toString();
|
||||
});
|
||||
cells.toArray().forEach(cell => {
|
||||
cell.style.background = bgColor.toString();
|
||||
cell.style.border = borderColor ? '1px solid ' + borderColor.toString() : null;
|
||||
});
|
||||
cellDetails.toArray().forEach(cellDetail => {
|
||||
cellDetail.style.background = cellColor.toString();
|
||||
});
|
||||
}
|
||||
|
||||
protected getTableActions(targetObject: JobActionContext): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
let editAction = this._instantiationService.createInstance(EditJobAction);
|
||||
if (!targetObject.canEdit) {
|
||||
editAction.enabled = false;
|
||||
}
|
||||
actions.push(editAction);
|
||||
actions.push(this._instantiationService.createInstance(DeleteJobAction));
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected convertStepsToStepInfos(steps: azdata.AgentJobStep[], job: azdata.AgentJobInfo): azdata.AgentJobStepInfo[] {
|
||||
let result = [];
|
||||
steps.forEach(step => {
|
||||
let stepInfo: azdata.AgentJobStepInfo = {
|
||||
jobId: job.jobId,
|
||||
jobName: job.name,
|
||||
script: null,
|
||||
scriptName: null,
|
||||
stepName: step.stepName,
|
||||
subSystem: null,
|
||||
id: +step.stepId,
|
||||
failureAction: null,
|
||||
successAction: null,
|
||||
failStepId: null,
|
||||
successStepId: null,
|
||||
command: null,
|
||||
commandExecutionSuccessCode: null,
|
||||
databaseName: null,
|
||||
databaseUserName: null,
|
||||
server: null,
|
||||
outputFileName: null,
|
||||
appendToLogFile: null,
|
||||
appendToStepHist: null,
|
||||
writeLogToTable: null,
|
||||
appendLogToTable: null,
|
||||
retryAttempts: null,
|
||||
retryInterval: null,
|
||||
proxyName: null
|
||||
};
|
||||
result.push(stepInfo);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getCurrentTableObject(rowIndex: number): JobActionContext {
|
||||
let data = this._table.grid.getData();
|
||||
if (!data || rowIndex >= data.getLength()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let jobId = data.getItem(rowIndex).jobId;
|
||||
if (!jobId) {
|
||||
// if we couldn't find the ID, check if it's an
|
||||
// error row
|
||||
let isErrorRow: boolean = data.getItem(rowIndex).id.indexOf('error') >= 0;
|
||||
if (isErrorRow) {
|
||||
jobId = data.getItem(rowIndex - 1).jobId;
|
||||
}
|
||||
}
|
||||
|
||||
let job: azdata.AgentJobInfo[] = this.jobs.filter(job => {
|
||||
return job.jobId === jobId;
|
||||
});
|
||||
|
||||
if (job && job.length > 0) {
|
||||
// add steps
|
||||
if (this.jobSteps && this.jobSteps[jobId]) {
|
||||
let steps = this.jobSteps[jobId];
|
||||
job[0].jobSteps = steps;
|
||||
}
|
||||
|
||||
// add schedules
|
||||
if (this.jobSchedules && this.jobSchedules[jobId]) {
|
||||
let schedules = this.jobSchedules[jobId];
|
||||
job[0].jobSchedules = schedules;
|
||||
}
|
||||
// add alerts
|
||||
if (this.jobAlerts && this.jobAlerts[jobId]) {
|
||||
let alerts = this.jobAlerts[jobId];
|
||||
job[0].alerts = alerts;
|
||||
}
|
||||
|
||||
if (job[0].jobSteps && job[0].jobSchedules && job[0].alerts) {
|
||||
return { job: job[0], canEdit: true };
|
||||
}
|
||||
return { job: job[0], canEdit: false };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async openCreateJobDialog() {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
await this._commandService.executeCommand('agent.openJobDialog', ownerUri);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>jobalert</title><path d="M16,2.24V7.58A5.38,5.38,0,0,0,15,6.5V4.3L12.8,5.39l-.64-.11a5,5,0,0,0-.65,0l-.36,0-.36,0,4.11-2.05H1.12L7.87,6.61,7.49,7a4.7,4.7,0,0,0-.34.39L1,4.3v6.94H6a4.64,4.64,0,0,0,.07.5c0,.17.07.33.11.5H0v-10Zm-4.5,4a4.35,4.35,0,0,1,1.75.36A4.53,4.53,0,0,1,15.64,9a4.49,4.49,0,0,1,0,3.5,4.53,4.53,0,0,1-2.39,2.39,4.49,4.49,0,0,1-3.5,0,4.53,4.53,0,0,1-2.39-2.39,4.49,4.49,0,0,1,0-3.5A4.53,4.53,0,0,1,9.75,6.59,4.35,4.35,0,0,1,11.5,6.24Zm0,8A3.38,3.38,0,0,0,12.86,14a3.53,3.53,0,0,0,1.86-1.86,3.49,3.49,0,0,0,0-2.73,3.53,3.53,0,0,0-1.86-1.86,3.49,3.49,0,0,0-2.73,0A3.53,3.53,0,0,0,8.28,9.37a3.49,3.49,0,0,0,0,2.73A3.53,3.53,0,0,0,10.14,14,3.38,3.38,0,0,0,11.5,14.24Zm-.5-6h1v3H11Zm0,4h1v1H11Z"/></svg>
|
||||
|
After Width: | Height: | Size: 815 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>jobalert_inverse</title><path class="cls-1" d="M16,2.24V7.58A5.38,5.38,0,0,0,15,6.5V4.3L12.8,5.39l-.64-.11a5,5,0,0,0-.65,0l-.36,0-.36,0,4.11-2.05H1.12L7.87,6.61,7.49,7a4.7,4.7,0,0,0-.34.39L1,4.3v6.94H6a4.64,4.64,0,0,0,.07.5c0,.17.07.33.11.5H0v-10Zm-4.5,4a4.35,4.35,0,0,1,1.75.36A4.53,4.53,0,0,1,15.64,9a4.49,4.49,0,0,1,0,3.5,4.53,4.53,0,0,1-2.39,2.39,4.49,4.49,0,0,1-3.5,0,4.53,4.53,0,0,1-2.39-2.39,4.49,4.49,0,0,1,0-3.5A4.53,4.53,0,0,1,9.75,6.59,4.35,4.35,0,0,1,11.5,6.24Zm0,8A3.38,3.38,0,0,0,12.86,14a3.53,3.53,0,0,0,1.86-1.86,3.49,3.49,0,0,0,0-2.73,3.53,3.53,0,0,0-1.86-1.86,3.49,3.49,0,0,0-2.73,0A3.53,3.53,0,0,0,8.28,9.37a3.49,3.49,0,0,0,0,2.73A3.53,3.53,0,0,0,10.14,14,3.38,3.38,0,0,0,11.5,14.24Zm-.5-6h1v3H11Zm0,4h1v1H11Z"/></svg>
|
||||
|
After Width: | Height: | Size: 883 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:#212121;}</style></defs><title>back_16x16</title><path class="cls-1" d="M16.15,8.5H2.1l6.15,6.15-.7.7L.19,8,7.55.65l.7.7L2.1,7.5h14Z"/></svg>
|
||||
|
After Width: | Height: | Size: 259 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>back_inverse_16x16</title><path class="cls-1" d="M16.15,8.5H2.1l6.15,6.15-.7.7L.19,8,7.55.65l.7.7L2.1,7.5h14Z"/></svg>
|
||||
|
After Width: | Height: | Size: 264 B |
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.detailView-toggle
|
||||
{
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.detailView-toggle.expand
|
||||
{
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: url(../images/arrow-right.gif) no-repeat center center;
|
||||
}
|
||||
|
||||
.detailView-toggle.collapse
|
||||
{
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: url(../images/sort-desc.gif) no-repeat center center;
|
||||
}
|
||||
.dynamic-cell-detail
|
||||
{
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
background-color: #dae5e8;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dynamic-cell-detail > :first-child
|
||||
{
|
||||
vertical-align: middle;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
.dynamic-cell-detail > .detail-container {
|
||||
overflow: auto;
|
||||
display: block !important;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;clip-rule:evenodd;}.cls-2{clip-path:url(#clip-path);}.cls-3{fill:#d02e00;}</style><clipPath id="clip-path"><path class="cls-1" d="M8.88,8l2.67-2.67-.88-.88L8,7.12,5.33,4.45l-.88.88L7.12,8,4.45,10.67l.88.88L8,8.88l2.67,2.67.88-.88ZM8,0a7.92,7.92,0,0,1,4,1.09A8.15,8.15,0,0,1,14.91,4a8,8,0,0,1,0,8.07A8.15,8.15,0,0,1,12,14.91a8,8,0,0,1-8.07,0A8.15,8.15,0,0,1,1.09,12,8,8,0,0,1,1.09,4,8.15,8.15,0,0,1,4,1.09,7.92,7.92,0,0,1,8,0Z"/></clipPath></defs><title>failed</title><g class="cls-2"><rect class="cls-3" x="-5" y="-5" width="26" height="26"/></g></svg>
|
||||
|
After Width: | Height: | Size: 718 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>job</title><path d="M11,4h5v9H0V4H5V2h6Zm4,1H1V7H4V6H5V7h6V6h1V7h3ZM1,12H15V8H12V9H11V8H5V9H4V8H1ZM6,3V4h4V3Z"/></svg>
|
||||
|
After Width: | Height: | Size: 218 B |
@@ -0,0 +1,293 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
jobhistory-component .all-jobs {
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.overview-container .overview-tab .resultsViewCollapsible {
|
||||
padding: 15px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.job-heading {
|
||||
text-align: left;
|
||||
padding-left: 13px;
|
||||
font-size: 1.5vw;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.vs-dark .overview-container .overview-tab {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.hc-black .overview-container .overview-tab {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.overview-container > .overview-tab {
|
||||
position: relative;
|
||||
margin-bottom: 1px;
|
||||
width: 100%;
|
||||
color: #4a4a4a;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input#accordion {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.vs-dark .overview-container .overview-tab label {
|
||||
background: #444444;
|
||||
}
|
||||
|
||||
.hc-black .overview-container .overview-tab label {
|
||||
background: #000000;
|
||||
border: 1px solid #2b56f2;
|
||||
}
|
||||
|
||||
.overview-container .overview-tab label {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 0 0 0 1em;
|
||||
background: #f4f4f4;
|
||||
font-weight: bold;
|
||||
line-height: 3;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vs-dark .overview-tab .accordion-content {
|
||||
background: #333333;
|
||||
}
|
||||
|
||||
.hc-black .overview-tab .accordion-content {
|
||||
background: #000000;
|
||||
border: 1px solid #2b56f2;
|
||||
}
|
||||
|
||||
.overview-tab .accordion-content {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
background: #eaeaea;
|
||||
-webkit-transition: max-height .35s;
|
||||
-o-transition: max-height .35s;
|
||||
transition: max-height .35s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.overview-tab .accordion-content p {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
/* :checked */
|
||||
input#accordion:checked ~ .accordion-content {
|
||||
max-height: 10em;
|
||||
}
|
||||
|
||||
/* Icon */
|
||||
.overview-container .overview-tab label::after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
line-height: 3;
|
||||
text-align: center;
|
||||
-webkit-transition: all .3s;
|
||||
-o-transition: all .3s;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.all-jobs > .back-button-icon {
|
||||
content: url('../common/media/back.svg');
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
padding-left: 13px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.vs-dark.monaco-shell .all-jobs >.back-button-icon,
|
||||
.hc-black.monaco-shell .all-jobs >.back-button-icon {
|
||||
content: url('../common/media/back_inverse.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');
|
||||
}
|
||||
|
||||
jobhistory-component .hc-black .icon.edit,
|
||||
jobhistory-component .vs-dark .icon.edit {
|
||||
background-image: url('../../../media/icons/edit_inverse.svg');
|
||||
}
|
||||
|
||||
jobhistory-component .vs .icon.edit {
|
||||
background-image: url('../../../media/icons/edit.svg');
|
||||
}
|
||||
|
||||
jobhistory-component .actions-container .icon.edit {
|
||||
background-position: 0% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 12px;
|
||||
}
|
||||
|
||||
a.action-label.icon.runJobIcon.non-runnable {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
a.action-label.icon.stopJobIcon.non-runnable {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.accordion-content #col1,
|
||||
.accordion-content #col2,
|
||||
.accordion-content #col3,
|
||||
.accordion-content #col4 {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.accordion-content #col1,
|
||||
.accordion-content #col3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.accordion-content #col2 {
|
||||
padding-right: 300px;
|
||||
}
|
||||
|
||||
table.step-list tr.step-row td {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.history-details {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.history-details > .job-steps {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
border-left: 3px solid #f4f4f4;
|
||||
padding-left: 10px;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vs-dark .history-details > .job-steps {
|
||||
border-left: 3px solid #444444;
|
||||
}
|
||||
|
||||
.hc-black .history-details > .job-steps {
|
||||
border-left: 3px solid #2b56f2;
|
||||
}
|
||||
|
||||
.history-details > .job-steps > table.step-list {
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
flex: 1 1;
|
||||
overflow: scroll;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.step-table .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
|
||||
background: none;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.step-table .monaco-tree.focused .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children.selected:not(.loading) > .content:before {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.step-table .list-row .status-icon {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.step-table .list-row .label {
|
||||
padding-left: 10px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.job-passed {
|
||||
background: green;
|
||||
}
|
||||
|
||||
.job-failed {
|
||||
background: red;
|
||||
}
|
||||
|
||||
.job-unknown {
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
.date-column {
|
||||
padding-left: 50px;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.step-table {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.prev-run-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
jobhistory-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
jobhistory-component > .jobhistory-heading-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding-top: 16px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
jobhistory-component > .agent-actionbar-container {
|
||||
border-top: 3px solid #f4f4f4;
|
||||
}
|
||||
|
||||
.vs-dark jobhistory-component > .agent-actionbar-container {
|
||||
border-top: 3px solid #444444;
|
||||
}
|
||||
|
||||
.hc-black jobhistory-component > .agent-actionbar-container {
|
||||
border-top: 3px solid #2b56f2;
|
||||
}
|
||||
|
||||
jobhistory-component .step-table.prev-run-list .monaco-tree-wrapper .monaco-tree-row {
|
||||
width: 96%;
|
||||
}
|
||||
|
||||
jobhistory-component .agent-actionbar-container > .monaco-toolbar.carbon-taskbar {
|
||||
margin: 10px 0px 5px 0px;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.steps-tree .list-row .status-icon.step-passed {
|
||||
content: url("../common/media/success_complete.svg");
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.steps-tree .list-row .status-icon.step-failed {
|
||||
content: url("../common/media/failed.svg");
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.steps-tree .list-row .label {
|
||||
padding-left: 10px;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
user-select: initial;
|
||||
}
|
||||
|
||||
.steps-tree .list-row {
|
||||
display: inline-flex;
|
||||
height: 20px
|
||||
}
|
||||
|
||||
.step-columns {
|
||||
padding-left: 50px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.steps-tree .tree-id-col {
|
||||
padding-left: 10px;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.steps-tree .tree-name-col {
|
||||
padding-right: 10px;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.steps-tree .tree-message-col {
|
||||
padding-right: 10px;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.steps-tree .step-column-heading {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.steps-header > .steps-icon {
|
||||
height: 25px;
|
||||
padding-right: 10px;
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.vs-dark .steps-header > .steps-icon,
|
||||
.hc-black .steps-header > .steps-icon {
|
||||
content: url("../common/media/step_inverse.svg");
|
||||
}
|
||||
|
||||
.steps-header > .steps-icon {
|
||||
content: url("../common/media/step.svg");
|
||||
}
|
||||
|
||||
jobstepsview-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row {
|
||||
width: 99.2%;
|
||||
}
|
||||
|
||||
.vs-dark jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row,
|
||||
.vs-dark jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused,
|
||||
.vs-dark jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.selected,
|
||||
.vs-dark jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused.selected {
|
||||
background: #444444 !important;
|
||||
}
|
||||
|
||||
.hc-black jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row,
|
||||
.hc-black jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.selected,
|
||||
.hc-black jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused,
|
||||
.hc-black jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused.selected {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row,
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused,
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.selected,
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused.selected {
|
||||
background: #dcdcdc !important;
|
||||
cursor: none;
|
||||
padding-left: 0px;
|
||||
}
|
||||
@@ -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>job_inverse</title><path class="cls-1" d="M11,4h5v9H0V4H5V2h6Zm4,1H1V7H4V6H5V7h6V6h1V7h3ZM1,12H15V8H12V9H11V8H5V9H4V8H1ZM6,3V4h4V3Z"/></svg>
|
||||
|
After Width: | Height: | Size: 286 B |
@@ -0,0 +1,416 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
agentview-component {
|
||||
height: 100%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
jobsview-component {
|
||||
height: 100%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
jobhistory-component {
|
||||
height: 100%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.job-heading-container {
|
||||
height: 50px;
|
||||
border-bottom: 3px solid #f4f4f4;
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
.vs-dark .job-heading-container {
|
||||
border-bottom: 3px solid #444444;
|
||||
}
|
||||
|
||||
.hc-black .job-heading-container {
|
||||
border-bottom: 3px solid #2b56f2;
|
||||
}
|
||||
|
||||
.jobview-grid {
|
||||
height: calc(100% - 75px);
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.vs-dark #agentViewDiv .slick-header-column {
|
||||
background: #333333 !important;
|
||||
}
|
||||
|
||||
#agentViewDiv .slick-header-column {
|
||||
background-color: transparent !important;
|
||||
border: 0px !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hc-black #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
|
||||
border: 1px solid #2b56f2;
|
||||
}
|
||||
|
||||
#jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
|
||||
border-right: transparent !important;
|
||||
border-left: transparent !important;
|
||||
line-height: 33px !important;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-joblist {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-jobnametable {
|
||||
border: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-jobnameindicatorsuccess {
|
||||
width: 5px;
|
||||
background: green;
|
||||
}
|
||||
|
||||
#jobsDiv .slick-cell.l1.r1 .jobview-jobnameindicatorfailure {
|
||||
width: 5px;
|
||||
background: red;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-jobnameindicatorcancel {
|
||||
width: 5px;
|
||||
background: orange;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid .jobview-jobnameindicatorunknown {
|
||||
width: 5px;
|
||||
background: grey;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid .slick-cell.l1.r1.error-row .jobview-jobnametext {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid .slick-cell.l1.r1 .jobview-jobnametext {
|
||||
text-overflow: ellipsis;
|
||||
width: 250px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#operatorsDiv .joboperatorsview-grid .slick-cell.l1.r1 .operatorview-operatornametext,
|
||||
#alertsDiv .jobalertsview-grid .slick-cell.l1.r1 .alertview-alertnametext,
|
||||
#proxiesDiv .jobproxiesview-grid .slick-cell.l1.r1 .proxyview-proxynametext {
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#jobsDiv .job-with-error {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-splitter {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-jobitem {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
flex-flow: row wrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-label {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-highlight-none {
|
||||
width: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#jobsDiv .detail-container {
|
||||
max-height: 100px !important;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#jobsDiv .detail {
|
||||
padding: 5px
|
||||
}
|
||||
|
||||
#jobsDiv .preload {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#jobsDiv .dynamic-cell-detail > :first-child {
|
||||
vertical-align: middle;
|
||||
line-height: 13px;
|
||||
padding: 10px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.jobsview-icon {
|
||||
background-image: url('./job.svg');
|
||||
}
|
||||
|
||||
.vs-dark .jobsview-icon,
|
||||
.hc-black .jobsview-icon {
|
||||
background-image: url('./job_inverse.svg');
|
||||
}
|
||||
|
||||
.alertsview-icon {
|
||||
background-image: url('./alert.svg');
|
||||
}
|
||||
|
||||
.vs-dark .alertsview-icon,
|
||||
.hc-black .alertsview-icon {
|
||||
background-image: url('./alert_inverse.svg');
|
||||
}
|
||||
|
||||
.proxiesview-icon {
|
||||
background-image: url('./proxy.svg');
|
||||
}
|
||||
|
||||
.vs-dark .proxiesview-icon,
|
||||
.hc-black .proxiesview-icon {
|
||||
background-image: url('./proxy_inverse.svg');
|
||||
}
|
||||
|
||||
.operatorsview-icon {
|
||||
background-image: url('./operator.svg');
|
||||
}
|
||||
|
||||
.vs-dark .operatorsview-icon,
|
||||
.hc-black .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;
|
||||
}
|
||||
|
||||
.vs-dark .jobview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted white;
|
||||
}
|
||||
|
||||
.hc-black .jobview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted #2b56f2;
|
||||
}
|
||||
|
||||
.jobview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted #444444;
|
||||
}
|
||||
|
||||
.job-heading-container > .icon.in-progress {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
padding-top: 16px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#jobsDiv jobsview-component .jobview-grid .slick-cell.l1.r1.error-row td.jobview-jobnameindicatorfailure {
|
||||
width: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
|
||||
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
|
||||
background: #dcdcdc !important;
|
||||
}
|
||||
|
||||
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
|
||||
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
|
||||
background: #444444 !important;
|
||||
}
|
||||
|
||||
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
|
||||
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.jobview-grid .slick-cell.l10.r10 {
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
table.jobprevruns {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
table.jobprevruns > tbody {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
#alertsDiv .jobalertsview-grid {
|
||||
height: calc(100% - 75px);
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#operatorsDiv .joboperatorsview-grid {
|
||||
height: calc(100% - 75px);
|
||||
width : 100%;
|
||||
display: block;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#proxiesDiv .jobproxiesview-grid {
|
||||
height: calc(100% - 75px);
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.vs .action-label.icon.refreshIcon {
|
||||
background-image: url('refresh.svg');
|
||||
}
|
||||
|
||||
.vs-dark .action-label.icon.refreshIcon,
|
||||
.hc-black .action-label.icon.refreshIcon {
|
||||
background-image: url('refresh_inverse.svg');
|
||||
}
|
||||
|
||||
.agent-actionbar-container .monaco-action-bar > ul.actions-container > li.action-item {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
jobsview-component .jobview-grid .slick-cell.error-row {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#alertsDiv jobalertsview-component .jobalertsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
|
||||
border-right: transparent !important;
|
||||
border-left: transparent !important;
|
||||
line-height: 33px !important;
|
||||
}
|
||||
|
||||
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
|
||||
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
|
||||
background: #dcdcdc !important;
|
||||
}
|
||||
|
||||
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
|
||||
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
|
||||
background: #444444 !important;
|
||||
}
|
||||
|
||||
.vs-dark .jobalertsview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted white;
|
||||
}
|
||||
|
||||
.jobalertsview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted #444444;
|
||||
}
|
||||
|
||||
#operatorsDiv joboperatorsview-component .joboperatorsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
|
||||
border-right: transparent !important;
|
||||
border-left: transparent !important;
|
||||
line-height: 33px !important;
|
||||
}
|
||||
|
||||
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
|
||||
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
|
||||
background: #dcdcdc !important;
|
||||
}
|
||||
|
||||
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
|
||||
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
|
||||
background: #444444 !important;
|
||||
}
|
||||
|
||||
.vs-dark .joboperatorsview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted white;
|
||||
}
|
||||
|
||||
.joboperatorsview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted #444444;
|
||||
}
|
||||
|
||||
#proxiesDiv jobproxiesview-component .jobproxiesview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
|
||||
border-right: transparent !important;
|
||||
border-left: transparent !important;
|
||||
line-height: 33px !important;
|
||||
}
|
||||
|
||||
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
|
||||
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
|
||||
background: #dcdcdc !important;
|
||||
}
|
||||
|
||||
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
|
||||
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
|
||||
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
|
||||
background: #444444 !important;
|
||||
}
|
||||
|
||||
.vs-dark .jobproxiesview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted white;
|
||||
}
|
||||
|
||||
.jobproxiesview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted #444444;
|
||||
}
|
||||
|
||||
.overview-container > .overview-tab > label {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#operatorsDiv .operatorview-operatornameindicatorenabled,
|
||||
#alertsDiv .alertview-alertnameindicatorenabled,
|
||||
#proxiesDiv .proxyview-proxynameindicatorenabled {
|
||||
width: 5px;
|
||||
background: green;
|
||||
}
|
||||
|
||||
#operatorsDiv .operatorview-operatornameindicatordisabled,
|
||||
#alertsDiv .alertview-alertnameindicatordisabled,
|
||||
#proxiesDiv .proxyview-proxynameindicatordisabled {
|
||||
width: 5px;
|
||||
background: red;
|
||||
}
|
||||
|
||||
#jobsDiv jobsview-component .monaco-toolbar.carbon-taskbar,
|
||||
#operatorsDiv joboperatorsview-component .monaco-toolbar.carbon-taskbar,
|
||||
#alertsDiv jobalertsview-component .monaco-toolbar.carbon-taskbar,
|
||||
#proxiesDiv jobproxiesview-component .monaco-toolbar.carbon-taskbar {
|
||||
margin: 10px 0px 10px 0px;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>operator</title><path d="M10.36,9.41a7.54,7.54,0,0,1,1.9,1.05A7,7,0,0,1,13.73,12,6.9,6.9,0,0,1,15,16H14a5.79,5.79,0,0,0-.47-2.33,6.07,6.07,0,0,0-3.2-3.2A5.79,5.79,0,0,0,8,10a5.79,5.79,0,0,0-2.33.47,6.07,6.07,0,0,0-3.2,3.2A5.79,5.79,0,0,0,2,16H1a6.89,6.89,0,0,1,2.74-5.54,7.54,7.54,0,0,1,1.9-1.05A5.07,5.07,0,0,1,4,8a4.24,4.24,0,0,1-.73-.06,1.92,1.92,0,0,1-.64-.23,1.25,1.25,0,0,1-.45-.46A1.48,1.48,0,0,1,2,6.5v-3a1.47,1.47,0,0,1,.12-.59,1.49,1.49,0,0,1,.8-.8A1.47,1.47,0,0,1,3.5,2a1.4,1.4,0,0,1,.45.07,4.88,4.88,0,0,1,.8-.87,5.21,5.21,0,0,1,1-.65A4.95,4.95,0,0,1,8,0,4.88,4.88,0,0,1,9.95.39a5,5,0,0,1,2.66,2.66A4.88,4.88,0,0,1,13,5a4.93,4.93,0,0,1-.18,1.34,5,5,0,0,1-.53,1.23,5.12,5.12,0,0,1-.83,1A4.73,4.73,0,0,1,10.36,9.41ZM3,6.5a.51.51,0,0,0,.5.5H4V3.5a.5.5,0,0,0-.85-.35A.48.48,0,0,0,3,3.5ZM5.35,8a3.92,3.92,0,0,0,1.23.74A4,4,0,0,0,8,9a3.85,3.85,0,0,0,1.55-.32,4.05,4.05,0,0,0,2.13-2.13A3.85,3.85,0,0,0,12,5a3.85,3.85,0,0,0-.32-1.55A4.05,4.05,0,0,0,9.55,1.32,3.85,3.85,0,0,0,8,1a4,4,0,0,0-1.83.45A4,4,0,0,0,4.75,2.67a1.47,1.47,0,0,1,.18.51A5.75,5.75,0,0,1,5,3.91q0,.41,0,.85t0,.87q0,.42,0,.78T5,7H7.5a.5.5,0,0,1,0,1Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 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>operator_inverse</title><path class="cls-1" d="M10.36,9.41a7.54,7.54,0,0,1,1.9,1.05A7,7,0,0,1,13.73,12,6.9,6.9,0,0,1,15,16H14a5.79,5.79,0,0,0-.47-2.33,6.07,6.07,0,0,0-3.2-3.2A5.79,5.79,0,0,0,8,10a5.79,5.79,0,0,0-2.33.47,6.07,6.07,0,0,0-3.2,3.2A5.79,5.79,0,0,0,2,16H1a6.89,6.89,0,0,1,2.74-5.54,7.54,7.54,0,0,1,1.9-1.05A5.07,5.07,0,0,1,4,8a4.24,4.24,0,0,1-.73-.06,1.92,1.92,0,0,1-.64-.23,1.25,1.25,0,0,1-.45-.46A1.48,1.48,0,0,1,2,6.5v-3a1.47,1.47,0,0,1,.12-.59,1.49,1.49,0,0,1,.8-.8A1.47,1.47,0,0,1,3.5,2a1.4,1.4,0,0,1,.45.07,4.88,4.88,0,0,1,.8-.87,5.21,5.21,0,0,1,1-.65A4.95,4.95,0,0,1,8,0,4.88,4.88,0,0,1,9.95.39a5,5,0,0,1,2.66,2.66A4.88,4.88,0,0,1,13,5a4.93,4.93,0,0,1-.18,1.34,5,5,0,0,1-.53,1.23,5.12,5.12,0,0,1-.83,1A4.73,4.73,0,0,1,10.36,9.41ZM3,6.5a.51.51,0,0,0,.5.5H4V3.5a.5.5,0,0,0-.85-.35A.48.48,0,0,0,3,3.5ZM5.35,8a3.92,3.92,0,0,0,1.23.74A4,4,0,0,0,8,9a3.85,3.85,0,0,0,1.55-.32,4.05,4.05,0,0,0,2.13-2.13A3.85,3.85,0,0,0,12,5a3.85,3.85,0,0,0-.32-1.55A4.05,4.05,0,0,0,9.55,1.32,3.85,3.85,0,0,0,8,1a4,4,0,0,0-1.83.45A4,4,0,0,0,4.75,2.67a1.47,1.47,0,0,1,.18.51A5.75,5.75,0,0,1,5,3.91q0,.41,0,.85t0,.87q0,.42,0,.78T5,7H7.5a.5.5,0,0,1,0,1Z"/></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"><title>proxy_account</title><path d="M12.23,7a3,3,0,0,0-3.39-.77,3,3,0,0,0-1.6,1.6A2.93,2.93,0,0,0,7,9a3,3,0,0,1-1.23,2.41,4.31,4.31,0,0,1,.66.42,4.2,4.2,0,0,1,.57.53V15a2.93,2.93,0,0,0-.23-1.16,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.93,2.93,0,0,0,1,15H0a3.92,3.92,0,0,1,.16-1.11,4.11,4.11,0,0,1,.45-1,3.87,3.87,0,0,1,.7-.84,4.2,4.2,0,0,1,.92-.63,3,3,0,0,1-1-3.58,3,3,0,0,1,1.6-1.6A2.92,2.92,0,0,1,4,6,3,3,0,0,1,6.41,7.23,4.12,4.12,0,0,1,8.23,5.41,3,3,0,0,1,7,3a2.93,2.93,0,0,1,.23-1.16A3,3,0,0,1,8.83.23a3,3,0,0,1,2.33,0,3,3,0,0,1,1.6,1.6,3,3,0,0,1-.09,2.52,2.94,2.94,0,0,1-.9,1.06,4.07,4.07,0,0,1,1,.67,4,4,0,0,1,.73.92ZM4,11a1.94,1.94,0,0,0,.78-.16A2,2,0,0,0,5.84,9.78a2,2,0,0,0,0-1.55A2,2,0,0,0,4.78,7.16a2,2,0,0,0-1.55,0A2,2,0,0,0,2.16,8.22a2,2,0,0,0,0,1.55,2,2,0,0,0,1.07,1.07A1.94,1.94,0,0,0,4,11ZM8,3a1.94,1.94,0,0,0,.16.78A2,2,0,0,0,9.22,4.84a2,2,0,0,0,1.55,0,2,2,0,0,0,1.07-1.07,2,2,0,0,0,0-1.55,2,2,0,0,0-1.07-1.07,2,2,0,0,0-1.55,0A2,2,0,0,0,8.16,2.22,1.94,1.94,0,0,0,8,3Zm8,7v6H8V10h2V8h4v2Zm-1,1H9v1h6Zm0,2H14v1H13V13H11v1H10V13H9v2h6Zm-4-3h2V9H11Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 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>proxy_account_inverse</title><path class="cls-1" d="M12.23,7a3,3,0,0,0-3.39-.77,3,3,0,0,0-1.6,1.6A2.93,2.93,0,0,0,7,9a3,3,0,0,1-1.23,2.41,4.31,4.31,0,0,1,.66.42,4.2,4.2,0,0,1,.57.53V15a2.93,2.93,0,0,0-.23-1.16,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.93,2.93,0,0,0,1,15H0a3.92,3.92,0,0,1,.16-1.11,4.11,4.11,0,0,1,.45-1A3.87,3.87,0,0,1,1.3,12a4.2,4.2,0,0,1,.92-.63,3,3,0,0,1-1-3.58,3,3,0,0,1,1.6-1.6A2.92,2.92,0,0,1,4,6,3,3,0,0,1,6.41,7.23,4.12,4.12,0,0,1,8.23,5.41,3,3,0,0,1,7,3a2.93,2.93,0,0,1,.23-1.16A3,3,0,0,1,8.83.23a3,3,0,0,1,2.33,0,3,3,0,0,1,1.6,1.6,3,3,0,0,1-.09,2.52,2.94,2.94,0,0,1-.9,1.06,4.07,4.07,0,0,1,1,.67,4,4,0,0,1,.73.92ZM4,11a1.94,1.94,0,0,0,.78-.16A2,2,0,0,0,5.84,9.78a2,2,0,0,0,0-1.55A2,2,0,0,0,4.78,7.16a2,2,0,0,0-1.55,0A2,2,0,0,0,2.16,8.22a2,2,0,0,0,0,1.55,2,2,0,0,0,1.07,1.07A1.94,1.94,0,0,0,4,11ZM8,3a1.94,1.94,0,0,0,.16.78A2,2,0,0,0,9.22,4.84a2,2,0,0,0,1.55,0,2,2,0,0,0,1.07-1.07,2,2,0,0,0,0-1.55,2,2,0,0,0-1.07-1.07,2,2,0,0,0-1.55,0A2,2,0,0,0,8.16,2.22,1.94,1.94,0,0,0,8,3Zm8,7v6H8V10h2V8h4v2Zm-1,1H9v1h6Zm0,2H14v1H13V13H11v1H10V13H9v2h6Zm-4-3h2V9H11Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 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:#212121;}</style></defs><title>refresh</title><path class="cls-1" d="M12.51,1.59a8.06,8.06,0,0,1,3.06,4A7.83,7.83,0,0,1,16,8.2a7.91,7.91,0,0,1-.29,2.12,8.13,8.13,0,0,1-.8,1.91A8,8,0,0,1,12,15.11a8.1,8.1,0,0,1-1.91.8,8.06,8.06,0,0,1-4.25,0A8.08,8.08,0,0,1,4,15.11a8,8,0,0,1-2.87-2.87,8.07,8.07,0,0,1-.8-1.91,8,8,0,0,1,0-4.25,8.11,8.11,0,0,1,.82-1.94,7.86,7.86,0,0,1,1.3-1.66A8,8,0,0,1,4.14,1.2H2V.2H6v4H5V1.88A7,7,0,0,0,1.28,6.24a7,7,0,0,0,0,3.82,7,7,0,0,0,1.8,3.09A7,7,0,0,0,6.14,15a7,7,0,0,0,3.71,0,7,7,0,0,0,1.67-.71,7,7,0,0,0,3.22-4.18,7,7,0,0,0-.13-4.12,7.07,7.07,0,0,0-2.68-3.52,6.78,6.78,0,0,0-2.07-1l.27-1A7.67,7.67,0,0,1,12.51,1.59Z"/></svg>
|
||||
|
After Width: | Height: | Size: 767 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>refresh_inverse</title><path class="cls-1" d="M12.51,1.59a8.06,8.06,0,0,1,3.06,4A7.83,7.83,0,0,1,16,8.2a7.91,7.91,0,0,1-.29,2.12,8.13,8.13,0,0,1-.8,1.91A8,8,0,0,1,12,15.11a8.1,8.1,0,0,1-1.91.8,8.06,8.06,0,0,1-4.25,0A8.08,8.08,0,0,1,4,15.11a8,8,0,0,1-2.87-2.87,8.07,8.07,0,0,1-.8-1.91,8,8,0,0,1,0-4.25,8.11,8.11,0,0,1,.82-1.94,7.86,7.86,0,0,1,1.3-1.66A8,8,0,0,1,4.14,1.2H2V.2H6v4H5V1.88A7,7,0,0,0,1.28,6.24a7,7,0,0,0,0,3.82,7,7,0,0,0,1.8,3.09A7,7,0,0,0,6.14,15a7,7,0,0,0,3.71,0,7,7,0,0,0,1.67-.71,7,7,0,0,0,3.22-4.18,7,7,0,0,0-.13-4.12,7.07,7.07,0,0,0-2.68-3.52,6.78,6.78,0,0,0-2.07-1l.27-1A7.67,7.67,0,0,1,12.51,1.59Z"/></svg>
|
||||
|
After Width: | Height: | Size: 772 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;clip-rule:evenodd;}.cls-2{clip-path:url(#clip-path);}</style><clipPath id="clip-path"><path class="cls-1" d="M1.14,10.29H3.43V8H1.14ZM4.57,6.86H6.86V4.57H4.57ZM1.14,3.43H3.43V1.14H1.14ZM8,3.43h2.29V1.14H8Zm-6.86,8H4.57V8H8V4.57h3.43V1.14h3.43V14.86H1.14ZM6.86,0V3.43H4.57V0H0V4.57H3.43V6.86H0V16H16V0Z"/></clipPath></defs><title>step</title><g class="cls-2"><rect x="-5.71" y="-5.71" width="27.43" height="27.43"/></g></svg>
|
||||
|
After Width: | Height: | Size: 590 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1,.cls-2,.cls-4{fill:#fff;}.cls-1{clip-rule:evenodd;}.cls-2{fill-rule:evenodd;}.cls-3{clip-path:url(#clip-path);}</style><clipPath id="clip-path"><path class="cls-1" d="M1.14,10.29H3.43V8H1.14ZM4.57,6.86H6.86V4.57H4.57ZM1.14,3.43H3.43V1.14H1.14ZM8,3.43h2.29V1.14H8Zm-6.86,8H4.57V8H8V4.57h3.43V1.14h3.43V14.86H1.14ZM6.86,0V3.43H4.57V0H0V4.57H3.43V6.86H0V16H16V0Z"/></clipPath></defs><title>step_inverse</title><path class="cls-2" d="M1.14,10.29H3.43V8H1.14ZM4.57,6.86H6.86V4.57H4.57ZM1.14,3.43H3.43V1.14H1.14ZM8,3.43h2.29V1.14H8Zm-6.86,8H4.57V8H8V4.57h3.43V1.14h3.43V14.86H1.14ZM6.86,0V3.43H4.57V0H0V4.57H3.43V6.86H0V16H16V0Z"/><g class="cls-3"><rect class="cls-4" x="-5.71" y="-5.71" width="27.43" height="27.43"/></g></svg>
|
||||
|
After Width: | Height: | Size: 878 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;clip-rule:evenodd;}.cls-2{clip-path:url(#clip-path);}.cls-3{fill:#3bb44a;}.cls-4{clip-path:url(#clip-path-2);}.cls-5{fill:#fff;}</style><clipPath id="clip-path"><path class="cls-1" d="M16,8a7.92,7.92,0,0,1-1.09,4A8.15,8.15,0,0,1,12,14.91a8,8,0,0,1-8.07,0A8.15,8.15,0,0,1,1.09,12,8,8,0,0,1,1.09,4,8.15,8.15,0,0,1,4,1.09a8,8,0,0,1,8.07,0A8.15,8.15,0,0,1,14.91,4,7.92,7.92,0,0,1,16,8Z"/></clipPath><clipPath id="clip-path-2"><polygon class="cls-1" points="10.9 4.9 11.6 5.6 6.5 10.71 3.65 7.85 4.35 7.15 6.5 9.29 10.9 4.9"/></clipPath></defs><title>success_complete</title><g class="cls-2"><rect class="cls-3" x="-5" y="-5" width="26" height="26"/></g><g class="cls-4"><rect class="cls-5" x="-1.35" y="-0.1" width="17.95" height="15.81"/></g></svg>
|
||||
|
After Width: | Height: | Size: 911 B |
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 #actionbarContainer class="agent-actionbar-container"></div>
|
||||
|
||||
<div #operatorsgrid class="joboperatorsview-grid"></div>
|
||||
@@ -0,0 +1,222 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/jobs';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as azdata from 'azdata';
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/electron-browser/agentView.component';
|
||||
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
|
||||
import { EditOperatorAction, DeleteOperatorAction, NewOperatorAction } from 'sql/platform/jobManagement/common/jobActions';
|
||||
import { JobManagementView } from 'sql/workbench/parts/jobManagement/electron-browser/jobManagementView';
|
||||
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
import { OperatorsCacheObject } from 'sql/platform/jobManagement/common/jobManagementService';
|
||||
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowDetailView';
|
||||
|
||||
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 extends JobManagementView implements OnInit, OnDestroy {
|
||||
|
||||
private columns: Array<Slick.Column<any>> = [
|
||||
{
|
||||
name: nls.localize('jobOperatorsView.name', 'Name'),
|
||||
field: 'name',
|
||||
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
|
||||
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: ROW_HEIGHT,
|
||||
enableCellNavigation: true,
|
||||
editable: false
|
||||
};
|
||||
|
||||
private dataView: any;
|
||||
private _isCloud: boolean;
|
||||
private _operatorsCacheObject: OperatorsCacheObject;
|
||||
|
||||
private _didTabChange: boolean;
|
||||
@ViewChild('operatorsgrid') _gridEl: ElementRef;
|
||||
|
||||
public operators: azdata.AgentOperatorInfo[];
|
||||
public contextAction = NewOperatorAction;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService,
|
||||
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
|
||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||
@Inject(IDashboardService) _dashboardService: IDashboardService
|
||||
) {
|
||||
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService, _agentViewComponent);
|
||||
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
|
||||
let operatorsCacheObject = this._jobManagementService.operatorsCacheObjectMap;
|
||||
let operatorsCache = operatorsCacheObject[this._serverName];
|
||||
if (operatorsCache) {
|
||||
this._operatorsCacheObject = operatorsCache;
|
||||
} else {
|
||||
this._operatorsCacheObject = new OperatorsCacheObject();
|
||||
this._operatorsCacheObject.serverName = this._serverName;
|
||||
this._jobManagementService.addToCache(this._serverName, this._operatorsCacheObject);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// set base class elements
|
||||
this._visibilityElement = this._gridEl;
|
||||
this._parentComponent = this._agentViewComponent;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._didTabChange = true;
|
||||
}
|
||||
|
||||
public layout() {
|
||||
let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
|
||||
if (height < 0) {
|
||||
height = 0;
|
||||
}
|
||||
|
||||
if (this._table) {
|
||||
this._table.layout(new dom.Dimension(
|
||||
dom.getContentWidth(this._gridEl.nativeElement),
|
||||
height));
|
||||
}
|
||||
}
|
||||
|
||||
onFirstVisible() {
|
||||
let self = this;
|
||||
|
||||
let cached: boolean = false;
|
||||
if (this._operatorsCacheObject.serverName === this._serverName) {
|
||||
if (this._operatorsCacheObject.operators && this._operatorsCacheObject.operators.length > 0) {
|
||||
cached = true;
|
||||
this.operators = this._operatorsCacheObject.operators;
|
||||
}
|
||||
}
|
||||
|
||||
let columns = this.columns.map((column) => {
|
||||
column.rerenderOnResize = true;
|
||||
return column;
|
||||
});
|
||||
|
||||
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
|
||||
let rowDetail = new RowDetailView({
|
||||
cssClass: '_detail_selector',
|
||||
useRowClick: false,
|
||||
panelRows: 1
|
||||
});
|
||||
columns.unshift(rowDetail.getColumnDefinition());
|
||||
|
||||
jQuery(this._gridEl.nativeElement).empty();
|
||||
jQuery(this.actionBarContainer.nativeElement).empty();
|
||||
this.initActionBar();
|
||||
this._table = new Table(this._gridEl.nativeElement, { columns }, this.options);
|
||||
this._table.grid.setData(this.dataView, true);
|
||||
|
||||
this._register(this._table.onContextMenu(e => {
|
||||
self.openContextMenu(e);
|
||||
}));
|
||||
|
||||
// check for cached state
|
||||
if (cached && this._agentViewComponent.refresh !== true) {
|
||||
this.onOperatorsAvailable(this.operators);
|
||||
this._showProgressWheel = false;
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
} else {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._jobManagementService.getOperators(ownerUri).then((result) => {
|
||||
if (result && result.operators) {
|
||||
self.operators = result.operators;
|
||||
self._operatorsCacheObject.operators = result.operators;
|
||||
self.onOperatorsAvailable(result.operators);
|
||||
} else {
|
||||
// TODO: handle error
|
||||
}
|
||||
this._showProgressWheel = false;
|
||||
if (this.isVisible && !this._didTabChange) {
|
||||
this._cd.detectChanges();
|
||||
} else if (this._didTabChange) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onOperatorsAvailable(operators: azdata.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._operatorsCacheObject.dataview = this.dataView;
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
}
|
||||
|
||||
protected getTableActions(): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
actions.push(this._instantiationService.createInstance(EditOperatorAction));
|
||||
actions.push(this._instantiationService.createInstance(DeleteOperatorAction));
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected getCurrentTableObject(rowIndex: number): any {
|
||||
return (this.operators && this.operators.length >= rowIndex)
|
||||
? this.operators[rowIndex]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private renderName(row, cell, value, columnDef, dataContext) {
|
||||
let resultIndicatorClass = dataContext.enabled ? 'operatorview-operatornameindicatorenabled' :
|
||||
'operatorview-operatornameindicatordisabled';
|
||||
return '<table class="operatorview-operatornametable"><tr class="operatorview-operatornamerow">' +
|
||||
'<td nowrap class=' + resultIndicatorClass + '></td>' +
|
||||
'<td nowrap class="operatorview-operatornametext">' + dataContext.name + '</td>' +
|
||||
'</tr></table>';
|
||||
}
|
||||
|
||||
public openCreateOperatorDialog() {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._commandService.executeCommand('agent.openOperatorDialog', ownerUri);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 #actionbarContainer class="agent-actionbar-container"></div>
|
||||
|
||||
<div #proxiesgrid class="jobproxiesview-grid"></div>
|
||||
@@ -0,0 +1,230 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/jobs';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/electron-browser/agentView.component';
|
||||
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
|
||||
import { EditProxyAction, DeleteProxyAction, NewProxyAction } from 'sql/platform/jobManagement/common/jobActions';
|
||||
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { JobManagementView } from 'sql/workbench/parts/jobManagement/electron-browser/jobManagementView';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
import { ProxiesCacheObject } from 'sql/platform/jobManagement/common/jobManagementService';
|
||||
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowDetailView';
|
||||
|
||||
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 extends JobManagementView implements OnInit, OnDestroy {
|
||||
|
||||
private NewProxyText: string = nls.localize('jobProxyToolbar-NewItem', "New Proxy");
|
||||
private RefreshText: string = nls.localize('jobProxyToolbar-Refresh', "Refresh");
|
||||
|
||||
private columns: Array<Slick.Column<any>> = [
|
||||
{
|
||||
name: nls.localize('jobProxiesView.accountName', 'Account Name'),
|
||||
field: 'accountName',
|
||||
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
|
||||
width: 200,
|
||||
id: 'accountName'
|
||||
},
|
||||
{ name: nls.localize('jobProxiesView.credentialName', 'Credential Name'), field: 'credentialName', width: 200, id: 'credentialName' },
|
||||
{ name: nls.localize('jobProxiesView.description', 'Description'), field: 'description', width: 200, id: 'description' },
|
||||
{ name: nls.localize('jobProxiesView.isEnabled', 'Enabled'), field: 'isEnabled', width: 200, id: 'isEnabled' }
|
||||
];
|
||||
|
||||
private options: Slick.GridOptions<any> = {
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: ROW_HEIGHT,
|
||||
enableCellNavigation: true,
|
||||
editable: false
|
||||
};
|
||||
|
||||
private dataView: any;
|
||||
private _isCloud: boolean;
|
||||
private _proxiesCacheObject: ProxiesCacheObject;
|
||||
|
||||
public proxies: azdata.AgentProxyInfo[];
|
||||
public readonly contextAction = NewProxyAction;
|
||||
|
||||
private _didTabChange: boolean;
|
||||
@ViewChild('proxiesgrid') _gridEl: ElementRef;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => AgentViewComponent)) _agentViewComponent: AgentViewComponent,
|
||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||
@Inject(ICommandService) private _commandService: ICommandService,
|
||||
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
|
||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||
@Inject(IDashboardService) _dashboardService: IDashboardService
|
||||
) {
|
||||
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService, _agentViewComponent);
|
||||
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
|
||||
let proxiesCacheObjectMap = this._jobManagementService.proxiesCacheObjectMap;
|
||||
let proxiesCacheObject = proxiesCacheObjectMap[this._serverName];
|
||||
if (proxiesCacheObject) {
|
||||
this._proxiesCacheObject = proxiesCacheObject;
|
||||
} else {
|
||||
this._proxiesCacheObject = new ProxiesCacheObject();
|
||||
this._proxiesCacheObject.serverName = this._serverName;
|
||||
this._jobManagementService.addToCache(this._serverName, this._proxiesCacheObject);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// set base class elements
|
||||
this._visibilityElement = this._gridEl;
|
||||
this._parentComponent = this._agentViewComponent;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._didTabChange = true;
|
||||
}
|
||||
|
||||
public layout() {
|
||||
let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
|
||||
if (height < 0) {
|
||||
height = 0;
|
||||
}
|
||||
|
||||
if (this._table) {
|
||||
this._table.layout(new dom.Dimension(
|
||||
dom.getContentWidth(this._gridEl.nativeElement),
|
||||
height));
|
||||
}
|
||||
}
|
||||
|
||||
onFirstVisible() {
|
||||
let self = this;
|
||||
let cached: boolean = false;
|
||||
if (this._proxiesCacheObject.serverName === this._serverName) {
|
||||
if (this._proxiesCacheObject.proxies && this._proxiesCacheObject.proxies.length > 0) {
|
||||
cached = true;
|
||||
this.proxies = this._proxiesCacheObject.proxies;
|
||||
}
|
||||
}
|
||||
|
||||
let columns = this.columns.map((column) => {
|
||||
column.rerenderOnResize = true;
|
||||
return column;
|
||||
});
|
||||
|
||||
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
|
||||
let rowDetail = new RowDetailView({
|
||||
cssClass: '_detail_selector',
|
||||
useRowClick: false,
|
||||
panelRows: 1
|
||||
});
|
||||
columns.unshift(rowDetail.getColumnDefinition());
|
||||
|
||||
jQuery(this._gridEl.nativeElement).empty();
|
||||
jQuery(this.actionBarContainer.nativeElement).empty();
|
||||
this.initActionBar();
|
||||
this._table = new Table(this._gridEl.nativeElement, { columns }, this.options);
|
||||
this._table.grid.setData(this.dataView, true);
|
||||
|
||||
this._register(this._table.onContextMenu(e => {
|
||||
self.openContextMenu(e);
|
||||
}));
|
||||
|
||||
// checked for cached state
|
||||
if (cached && this._agentViewComponent.refresh !== true) {
|
||||
self.onProxiesAvailable(this.proxies);
|
||||
this._showProgressWheel = false;
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
} else {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._jobManagementService.getProxies(ownerUri).then((result) => {
|
||||
if (result && result.proxies) {
|
||||
self.proxies = result.proxies;
|
||||
self._proxiesCacheObject.proxies = result.proxies;
|
||||
self.onProxiesAvailable(result.proxies);
|
||||
} else {
|
||||
// TODO: handle error
|
||||
}
|
||||
this._showProgressWheel = false;
|
||||
if (this.isVisible && !this._didTabChange) {
|
||||
this._cd.detectChanges();
|
||||
} else if (this._didTabChange) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onProxiesAvailable(proxies: azdata.AgentProxyInfo[]) {
|
||||
let items: any = proxies.map((item) => {
|
||||
return {
|
||||
id: item.accountName,
|
||||
accountName: item.accountName,
|
||||
credentialName: item.credentialName,
|
||||
description: item.description,
|
||||
isEnabled: item.isEnabled
|
||||
};
|
||||
});
|
||||
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(items);
|
||||
this.dataView.endUpdate();
|
||||
this._proxiesCacheObject.dataview = this.dataView;
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
}
|
||||
|
||||
protected getTableActions(): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
actions.push(this._instantiationService.createInstance(EditProxyAction));
|
||||
actions.push(this._instantiationService.createInstance(DeleteProxyAction));
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected getCurrentTableObject(rowIndex: number): any {
|
||||
return (this.proxies && this.proxies.length >= rowIndex)
|
||||
? this.proxies[rowIndex]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private renderName(row, cell, value, columnDef, dataContext) {
|
||||
let resultIndicatorClass = dataContext.isEnabled ? 'proxyview-proxynameindicatorenabled' :
|
||||
'proxyview-proxynameindicatordisabled';
|
||||
return '<table class="proxyview-proxynametable"><tr class="proxyview-proxynamerow">' +
|
||||
'<td nowrap class=' + resultIndicatorClass + '></td>' +
|
||||
'<td nowrap class="proxyview-proxynametext">' + dataContext.accountName + '</td>' +
|
||||
'</tr></table>';
|
||||
}
|
||||
|
||||
public openCreateProxyDialog() {
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._jobManagementService.getCredentials(ownerUri).then((result) => {
|
||||
if (result && result.credentials) {
|
||||
this._commandService.executeCommand('agent.openProxyDialog', ownerUri, undefined, result.credentials);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||