blob: cb1c283c3b38e5a5d0de530fd9a0dae35974faf5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Component, OnInit, ViewChild, Input, Output, EventEmitter, Inject } from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import { HealthStatusService } from '../../../core/services';
import { SortUtils } from '../../../core/util';
import { ConfirmationDialogType } from '../../../shared';
import { ConfirmationDialogComponent } from '../../../shared/modal-dialog/confirmation-dialog';
import { EnvironmentsDataService } from '../management-data.service';
import { EnvironmentModel, ManagementConfigModel } from '../management.model';
import {ProgressBarService} from '../../../core/services/progress-bar.service';
import {DetailDialogComponent} from '../../../resources/exploratory/detail-dialog';
export interface ManageAction {
action: string;
environment: string;
resource: string;
}
@Component({
selector: 'management-grid',
templateUrl: 'management-grid.component.html',
styleUrls: [
'./management-grid.component.scss',
'../../../resources/resources-grid/resources-grid.component.scss',
'../../../resources/computational/computational-resources-list/computational-resources-list.component.scss'
]
})
export class ManagementGridComponent implements OnInit {
allEnvironmentData: Array<any>;
allFilteredEnvironmentData: Array<any>;
loading: boolean = false;
filterConfiguration: ManagementConfigModel = new ManagementConfigModel([], '', [], [], [], []);
filterForm: ManagementConfigModel = new ManagementConfigModel([], '', [], [], [], []);
filtering: boolean = false;
collapsedFilterRow: boolean = false;
@Input() environmentsHealthStatuses: Array<any>;
@Input() resources: Array<any>;
@Input() isAdmin: boolean;
@Input() currentUser: string = '';
@Output() refreshGrid: EventEmitter<{}> = new EventEmitter();
@Output() actionToggle: EventEmitter<ManageAction> = new EventEmitter();
@Output() emitSelectedList: EventEmitter<ManageAction> = new EventEmitter();
displayedColumns: string[] = [ 'checkbox', 'user', 'type', 'project', 'shape', 'status', 'resources', 'actions'];
displayedFilterColumns: string[] = ['checkbox-filter', 'user-filter', 'type-filter', 'project-filter', 'shape-filter', 'status-filter', 'resource-filter', 'actions-filter'];
private selected;
private allActiveNotebooks: any;
constructor(
private healthStatusService: HealthStatusService,
private environmentsDataService: EnvironmentsDataService,
public toastr: ToastrService,
public dialog: MatDialog,
private progressBarService: ProgressBarService,
) { }
ngOnInit() {
this.getEnvironmentData();
}
getEnvironmentData() {
setTimeout(() => {this.progressBarService.startProgressBar(); } , 0);
this.environmentsDataService._data.subscribe(data => {
if (data) {
this.allEnvironmentData = EnvironmentModel.loadEnvironments(data);
this.getDefaultFilterConfiguration(data);
this.applyFilter(this.filterForm);
}
this.progressBarService.stopProgressBar();
}, () => {
this.progressBarService.stopProgressBar();
});
}
buildGrid(): void {
this.refreshGrid.emit();
this.filtering = false;
}
public onUpdate($event): void {
this.filterForm[$event.type] = $event.model;
}
public toggleFilterRow(): void {
this.collapsedFilterRow = !this.collapsedFilterRow;
}
public resetFilterConfigurations(): void {
this.filterForm.defaultConfigurations();
this.applyFilter(this.filterForm);
this.buildGrid();
}
public applyFilter(config) {
let filteredData = this.getEnvironmentDataCopy();
const containsStatus = (list, selectedItems) => {
if (list) {
return list.filter((item: any) => { if (selectedItems.indexOf(item.status) !== -1) return item; });
}
};
if (filteredData.length) this.filtering = true;
if (config) {
filteredData = filteredData.filter(item => {
const isUser = config.users.length > 0 ? (config.users.indexOf(item.user) !== -1) : true;
const isTypeName = item.name ? item.name.toLowerCase()
.indexOf(config.type.toLowerCase()) !== -1 : item.type.toLowerCase().indexOf(config.type.toLowerCase()) !== -1;
const isStatus = config.statuses.length > 0 ? (config.statuses.indexOf(item.status) !== -1) : (config.type !== 'active');
const isShape = config.shapes.length > 0 ? (config.shapes.indexOf(item.shape) !== -1) : true;
const isProject = config.projects.length > 0 ? (config.projects.indexOf(item.project) !== -1) : true;
const modifiedResources = containsStatus(item.resources, config.resources);
let isResources = config.resources.length > 0 ? (modifiedResources.length > 0) : true;
if (config.resources.length > 0 && modifiedResources.length > 0) { item.resources = modifiedResources; }
if (config.resources && config.resources.length === 0 && config.type === 'active' ||
modifiedResources && modifiedResources.length >= 0 && config.resources.length > 0 && config.type === 'active') {
item.resources = modifiedResources;
isResources = true;
}
return isUser && isTypeName && isStatus && isShape && isProject && isResources;
});
}
this.allFilteredEnvironmentData = filteredData;
this.allActiveNotebooks = this.allFilteredEnvironmentData.filter(v => v.name && (v.status === 'running' || v.status === 'stopped'));
}
getEnvironmentDataCopy() {
return this.allEnvironmentData;
}
toggleResourceAction(environment: any, action: string, resource?): void {
this.actionToggle.emit({ environment, action, resource });
}
isResourcesInProgress(notebook) {
if (notebook) {
if (notebook.name === 'edge node') {
return this.allEnvironmentData
.filter(env => env.user === notebook.user)
.some(el => this.inProgress([el]) || this.inProgress(el.resources));
} else if (notebook.resources && notebook.resources.length) {
return this.inProgress(notebook.resources);
}
}
return false;
}
public inProgress(resources) {
return resources.filter(resource => (
resource.status !== 'failed'
&& resource.status !== 'terminated'
&& resource.status !== 'running'
&& resource.status !== 'stopped')).length > 0;
}
public isActiveResources(item) {
if (item.resources.length) return item.resources.some(e => e.status !== 'terminated');
}
private getDefaultFilterConfiguration(data): void {
const users = [], projects = [], shapes = [], statuses = [], resources = [];
data && data.forEach((item: any) => {
if (item.user && users.indexOf(item.user) === -1) users.push(item.user);
if (item.status && statuses.indexOf(item.status.toLowerCase()) === -1) statuses.push(item.status.toLowerCase());
if (item.project && projects.indexOf(item.project) === -1) projects.push(item.project);
if (item.shape && shapes.indexOf(item.shape) === -1) shapes.push(item.shape);
if (item.computational_resources) {
item.computational_resources.map((resource: any) => {
if (resources.indexOf(resource.status) === -1) resources.push(resource.status);
resources.sort(SortUtils.statusSort);
});
}
});
this.filterConfiguration = new ManagementConfigModel(users, '', projects, shapes, statuses, resources);
}
openNotebookDetails(data) {
if (!data.exploratory_urls || !data.exploratory_urls.length) {
return;
}
this.dialog.open(DetailDialogComponent, { data:
{notebook: data, buckets: [], type: 'environment'},
panelClass: 'modal-lg'
})
.afterClosed().subscribe(() => {});
}
toggleActionForAll(element) {
element.isSelected = !element.isSelected;
this.selected = this.allFilteredEnvironmentData.filter(item => !!item.isSelected);
this.emitSelectedList.emit(this.selected);
}
toggleSelectionAll() {
if (this.selected && this.selected.length === this.allActiveNotebooks.length) {
this.allActiveNotebooks.forEach(notebook => notebook.isSelected = false);
} else {
this.allActiveNotebooks.forEach(notebook => notebook.isSelected = true);
}
this.selected = this.allFilteredEnvironmentData.filter(item => !!item.isSelected);
this.emitSelectedList.emit(this.selected);
}
}
@Component({
selector: 'confirm-dialog',
template: `
<div class="dialog-header">
<h4 class="modal-title"><span class="capitalize">{{ data.action }}</span> resource</h4>
<button type="button" class="close" (click)="dialogRef.close()">&times;</button>
</div>
<div mat-dialog-content class="content">
<div *ngIf="data.type === 'cluster'">
<p>Resource <span class="strong"> {{ data.resource_name }}</span> of user <span class="strong"> {{ data.user }} </span> will be
<span *ngIf="data.action === 'terminate'"> decommissioned.</span>
<span *ngIf="data.action === 'stop'">stopped.</span>
</p>
</div>
<div class="resource-list" *ngIf="data.type === 'notebook'">
<div class="resource-list-header">
<div class="resource-name">Notebook</div>
<div class="clusters-list">
<div class="clusters-list-item">
<div class="cluster"><span *ngIf="isClusterLength">Cluster</span></div>
<div class="status">Further status</div>
</div>
</div>
</div>
<div class="scrolling-content resource-heigth">
<div class="resource-list-row sans node" *ngFor="let notebook of notebooks">
<div class="resource-name ellipsis">
{{notebook.name}}
</div>
<div class="clusters-list">
<div class="clusters-list-item">
<div class="cluster"></div>
<div class="status"
[ngClass]="{
'stopped': data.action==='stop', 'terminated': data.action === 'terminate'
}"
>
{{data.action === 'stop' ? 'Stopped' : 'Terminated'}}
</div>
</div>
<div class="clusters-list-item" *ngFor="let cluster of notebook?.resources">
<div class="cluster">{{cluster.computational_name}}</div>
<div class="status" [ngClass]="{
'stopped': (data.action==='stop' && cluster.image==='docker.dlab-dataengine'), 'terminated': data.action === 'terminate' || (data.action==='stop' && cluster.image!=='docker.dlab-dataengine')
}">{{data.action === 'stop' && cluster.image === "docker.dlab-dataengine" ? 'Stopped' : 'Terminated'}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="text-center ">
<p class="strong">Do you want to proceed?</p>
</div>
<div class="text-center m-top-20">
<button type="button" class="butt" mat-raised-button (click)="dialogRef.close()">No</button>
<button type="button" class="butt butt-success" mat-raised-button (click)="dialogRef.close(true)">Yes</button>
</div>
`,
styles: [
`
.content { color: #718ba6; padding: 20px 50px; font-size: 14px; font-weight: 400; margin: 0; }
.info { color: #35afd5; }
.info .confirm-dialog { color: #607D8B; }
header { display: flex; justify-content: space-between; color: #607D8B; }
header h4 i { vertical-align: bottom; }
header a i { font-size: 20px; }
header a:hover i { color: #35afd5; cursor: pointer; }
.plur { font-style: normal; }
.scrolling-content{overflow-y: auto; max-height: 200px; }
.cluster { width: 50%; text-align: left;}
.status { width: 50%;text-align: left;}
.label { font-size: 15px; font-weight: 500; font-family: "Open Sans",sans-serif;}
.node { font-weight: 300;}
.resource-name { width: 40%;text-align: left; padding: 10px 0;line-height: 26px;}
.clusters-list { width: 60%;text-align: left; padding: 10px 0;line-height: 26px;}
.clusters-list-item { width: 100%;text-align: left;display: flex}
.resource-list{max-width: 100%; margin: 0 auto;margin-top: 20px; }
.resource-list-header{display: flex; font-weight: 600; font-size: 16px;height: 48px; border-top: 1px solid #edf1f5; border-bottom: 1px solid #edf1f5; padding: 0 20px;}
.resource-list-row{display: flex; border-bottom: 1px solid #edf1f5;padding: 0 20px;}
.confirm-resource-terminating{text-align: left; padding: 10px 20px;}
.confirm-message{color: #ef5c4b;font-size: 13px;min-height: 18px; text-align: center; padding-top: 20px}
.checkbox{margin-right: 5px;vertical-align: middle; margin-bottom: 3px;}
label{cursor: pointer}
.bottom-message{padding-top: 15px;}
.table-header{padding-bottom: 10px;}`
]
})
export class ReconfirmationDialogComponent {
private notebooks;
private isClusterLength;
constructor(
public dialogRef: MatDialogRef<ReconfirmationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {
if (data.notebooks && data.notebooks.length) {
this.notebooks = JSON.parse(JSON.stringify(data.notebooks));
this.notebooks = this.notebooks.map(notebook => {
notebook.resources = notebook.resources.filter(res => res.status !== 'terminated' && res.status.slice(0, 4) !== data.action);
if (notebook.resources.length) {
this.isClusterLength = true;
}
return notebook;
});
}
}
}