| /* |
| * 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 {Project} from '../../administration/project/project.component'; |
| import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; |
| import { animate, state, style, transition, trigger } from '@angular/animations'; |
| import { ToastrService } from 'ngx-toastr'; |
| import { MatDialog } from '@angular/material/dialog'; |
| import {ProjectService, UserResourceService} from '../../core/services'; |
| import { ExploratoryModel, Exploratory } from './resources-grid.model'; |
| import { FilterConfigurationModel } from './filter-configuration.model'; |
| import { GeneralEnvironmentStatus } from '../../administration/management/management.model'; |
| import { ConfirmationDialogType } from '../../shared'; |
| import { SortUtils, CheckUtils } from '../../core/util'; |
| import { DetailDialogComponent } from '../exploratory/detail-dialog'; |
| import { AmiCreateDialogComponent } from '../exploratory/ami-create-dialog'; |
| import { InstallLibrariesComponent } from '../exploratory/install-libraries'; |
| import { ComputationalResourceCreateDialogComponent } from '../computational/computational-resource-create-dialog/computational-resource-create-dialog.component'; |
| import { CostDetailsDialogComponent } from '../exploratory/cost-details-dialog'; |
| import { ConfirmationDialogComponent } from '../../shared/modal-dialog/confirmation-dialog'; |
| import { SchedulerComponent } from '../scheduler'; |
| import { DICTIONARY } from '../../../dictionary/global.dictionary'; |
| import {ProgressBarService} from '../../core/services/progress-bar.service'; |
| import {ComputationModel} from '../computational/computational-resource.model'; |
| import {NotebookModel} from '../exploratory/notebook.model'; |
| import {AuditService} from '../../core/services/audit.service'; |
| |
| export interface SharedEndpoint { |
| edge_node_ip: string; |
| shared_bucket_name?: string | null; |
| shared_container_name?: string | null; |
| status: string; |
| user_own_bucket_name?: string | null; |
| user_container_name?: string | null; |
| user_own_bicket_name?: string | null; |
| shared_storage_account_name?: string | null; |
| user_storage_account_name?: string | null; |
| } |
| |
| export interface ProjectEndpoint { |
| account: string; |
| cloudProvider: string; |
| endpoint_tag: string; |
| name: string; |
| status: string; |
| url: string; |
| } |
| |
| export interface BucketList { |
| name: string; |
| children: Bucket[]; |
| length?: number; |
| cloud: string; |
| } |
| |
| export interface Bucket { |
| name: string; |
| endpoint: string; |
| } |
| |
| @Component({ |
| selector: 'resources-grid', |
| templateUrl: 'resources-grid.component.html', |
| styleUrls: ['./resources-grid.component.scss'], |
| animations: [ |
| trigger('detailExpand', [ |
| state('collapsed', style({ height: '0px', minHeight: '0' })), |
| state('expanded', style({ height: '*' })), |
| transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), |
| ]), |
| ], |
| }) |
| |
| export class ResourcesGridComponent implements OnInit { |
| readonly DICTIONARY = DICTIONARY; |
| |
| @Input() projects: Array<any>; |
| @Output() getEnvironments: EventEmitter<any> = new EventEmitter(); |
| |
| environments: Exploratory[]; |
| |
| collapseFilterRow: boolean = false; |
| filtering: boolean = false; |
| activeFiltering: boolean = false; |
| activeProject: any; |
| healthStatus: GeneralEnvironmentStatus; |
| |
| filteredEnvironments: Exploratory[] = []; |
| filterConfiguration: FilterConfigurationModel = new FilterConfigurationModel('', [], [], [], '', ''); |
| filterForm: FilterConfigurationModel = new FilterConfigurationModel('', [], [], [], '', ''); |
| |
| public filteringColumns: Array<any> = [ |
| { title: 'Environment name', name: 'name', class: 'name-col', filter_class: 'name-filter', filtering: true }, |
| { title: 'Status', name: 'statuses', class: 'status-col', filter_class: 'status-filter', filtering: true }, |
| { title: 'Instance size', name: 'shapes', class: 'shape-col', filter_class: 'shape-filter', filtering: true }, |
| { title: 'Tags', name: 'tag', class: 'tag-col', filter_class: 'tag-filter', filtering: false }, |
| { title: 'Computational resource', name: 'resources', class: 'resources-col', filter_class: 'resource-filter', filtering: true }, |
| { title: 'Cost', name: 'cost', class: 'cost-col', filter_class: 'cost-filter', filtering: false }, |
| { title: '', name: 'actions', class: 'actions-col', filter_class: 'action-filter', filtering: false } |
| ]; |
| |
| public displayedColumns: string[] = this.filteringColumns.map(item => item.name); |
| public displayedFilterColumns: string[] = this.filteringColumns.map(item => item.filter_class); |
| public bucketsList: BucketList; |
| public activeProjectsList: any; |
| |
| constructor( |
| public toastr: ToastrService, |
| private userResourceService: UserResourceService, |
| private dialog: MatDialog, |
| private progressBarService: ProgressBarService, |
| private projectService: ProjectService, |
| private auditService: AuditService, |
| ) { } |
| |
| ngOnInit(): void { |
| this.buildGrid(); |
| this.getUserProjects(); |
| } |
| |
| public getUserProjects() { |
| this.projectService.getUserProjectsList(true).subscribe((projects: Project[]) => { |
| this.activeProjectsList = projects; |
| }); |
| } |
| |
| public buildGrid(): void { |
| setTimeout(() => {this.progressBarService.startProgressBar(); } , 0); |
| this.userResourceService.getUserProvisionedResources() |
| .subscribe((result: any) => { |
| this.environments = ExploratoryModel.loadEnvironments(result); |
| this.getEnvironments.emit(this.environments); |
| this.getBuckets(); |
| this.getDefaultFilterConfiguration(); |
| (this.environments.length) ? this.getUserPreferences() : this.filteredEnvironments = []; |
| this.healthStatus && !this.healthStatus.billingEnabled && this.modifyGrid(); |
| this.progressBarService.stopProgressBar(); |
| }, () => this.progressBarService.stopProgressBar()); |
| } |
| |
| public toggleFilterRow(): void { |
| this.collapseFilterRow = !this.collapseFilterRow; |
| } |
| |
| public onUpdate($event): void { |
| this.filterForm[$event.type] = $event.model; |
| } |
| |
| public selectActiveProject(project = ''): void { |
| this.filterForm.project = project; |
| this.applyFilter_btnClick(this.filterForm); |
| } |
| |
| public showActiveInstances(): void { |
| this.filterForm = this.loadUserPreferences(this.filterActiveInstances()); |
| this.applyFilter_btnClick(this.filterForm); |
| this.buildGrid(); |
| } |
| |
| public containsNotebook(notebook_name: string, envoirmentNames: Array<string>): boolean { |
| if (notebook_name && envoirmentNames.length ) { |
| return envoirmentNames |
| .some(item => CheckUtils.delimitersFiltering(notebook_name) === CheckUtils.delimitersFiltering(item)); |
| } |
| return false; |
| } |
| |
| |
| public isResourcesInProgress(notebook): boolean { |
| const env = this.getResourceByName(notebook.name, notebook.project); |
| |
| if (env && env.resources.length) { |
| return env.resources.filter(item => (item.status !== 'failed' && item.status !== 'terminated' |
| && item.status !== 'running' && item.status !== 'stopped')).length > 0; |
| } |
| return false; |
| } |
| |
| public filterActiveInstances(): FilterConfigurationModel { |
| return (<FilterConfigurationModel | any>Object).assign({}, this.filterConfiguration, { |
| statuses: SortUtils.activeStatuses(), |
| resources: SortUtils.activeStatuses(), |
| type: 'active', |
| project: this.activeProject || '' |
| }); |
| } |
| |
| public resetFilterConfigurations(): void { |
| this.filterForm.resetConfigurations(); |
| this.updateUserPreferences(this.filterForm); |
| this.buildGrid(); |
| } |
| |
| public printDetailEnvironmentModal(data): void { |
| this.dialog.open(DetailDialogComponent, { data: |
| {notebook: data, bucketStatus: this.healthStatus.bucketBrowser, buckets: this.bucketsList, type: 'resource'}, |
| panelClass: 'modal-lg' |
| }) |
| .afterClosed().subscribe(() => this.buildGrid()); |
| } |
| |
| public printCostDetails(data): void { |
| this.dialog.open(CostDetailsDialogComponent, { data: data, panelClass: 'modal-xl' }) |
| .afterClosed().subscribe(() => this.buildGrid()); |
| } |
| |
| public exploratoryAction(data, action: string): void { |
| const resource = this.getResourceByName(data.name, data.project); |
| |
| if (action === 'deploy') { |
| this.dialog.open(ComputationalResourceCreateDialogComponent, { data: { notebook: resource, full_list: this.environments }, panelClass: 'modal-xxl' }) |
| .afterClosed().subscribe((res) => { |
| res && this.buildGrid(); |
| }); |
| } else if (action === 'run') { |
| this.userResourceService |
| .runExploratoryEnvironment({ notebook_instance_name: data.name, project_name: data.project }) |
| .subscribe( |
| () => this.buildGrid(), |
| error => this.toastr.error(error.message || 'Exploratory starting failed!', 'Oops!')); |
| } else if (action === 'stop') { |
| this.dialog.open(ConfirmationDialogComponent, { data: { notebook: data, type: ConfirmationDialogType.StopExploratory }, panelClass: 'modal-sm' }) |
| .afterClosed().subscribe((res) => { |
| res && this.buildGrid(); |
| }); |
| } else if (action === 'terminate') { |
| this.dialog.open(ConfirmationDialogComponent, { data: |
| { notebook: data, type: ConfirmationDialogType.TerminateExploratory }, panelClass: 'modal-sm' }) |
| .afterClosed().subscribe((res) => res && this.buildGrid()); |
| } else if (action === 'install') { |
| this.dialog.open(InstallLibrariesComponent, { data: data, panelClass: 'modal-fullscreen' }) |
| .afterClosed().subscribe((res) => res && this.buildGrid()); |
| } else if (action === 'schedule') { |
| this.dialog.open(SchedulerComponent, { data: { notebook: data, type: 'EXPLORATORY' }, panelClass: 'modal-xl-s' }) |
| .afterClosed().subscribe((res) => res && this.buildGrid()); |
| } else if (action === 'ami') { |
| this.dialog.open(AmiCreateDialogComponent, { data: data, panelClass: 'modal-sm' }) |
| .afterClosed().subscribe((res) => res && this.buildGrid()); |
| } |
| } |
| |
| public getBuckets(): void { |
| const bucketsList = []; |
| this.environments.forEach(project => { |
| if (project.endpoints && project.endpoints.length !== 0) { |
| project.endpoints.forEach((endpoint: ProjectEndpoint) => { |
| if (endpoint.status === 'ACTIVE') { |
| const currEndpoint: SharedEndpoint = project.projectEndpoints[endpoint.name]; |
| const edgeItem: BucketList = {name: `${project.project} (${endpoint.name})`, children: [], cloud: endpoint.cloudProvider}; |
| let projectBucket: string = currEndpoint.user_own_bicket_name |
| || currEndpoint.user_own_bucket_name; |
| if (currEndpoint.user_container_name) { |
| projectBucket = currEndpoint.user_storage_account_name + '.' + currEndpoint.user_container_name; |
| } |
| let sharedBucket: string = currEndpoint.shared_bucket_name; |
| if (currEndpoint.shared_container_name) { |
| sharedBucket = currEndpoint.shared_storage_account_name + '.' + currEndpoint.shared_container_name; |
| } |
| if (projectBucket && currEndpoint.status !== 'terminated' |
| && currEndpoint.status !== 'terminating' && currEndpoint.status !== 'failed') { |
| edgeItem.children.push({name: projectBucket, endpoint: endpoint.name}); |
| } |
| if (sharedBucket) { |
| edgeItem.children.push({name: sharedBucket, endpoint: endpoint.name}); |
| } |
| bucketsList.push(edgeItem); |
| } |
| }); |
| } |
| }); |
| |
| this.bucketsList = SortUtils.flatDeep(bucketsList, 1).filter(v => v.children.length); |
| } |
| |
| |
| // PRIVATE |
| private getResourceByName(notebook_name: string, project_name: string) { |
| return this.getEnvironmentsListCopy().filter(environments => environments.project === project_name) |
| .map(env => env.exploratory.find(({ name }) => name === notebook_name)) |
| .filter(name => !!name)[0]; |
| } |
| |
| private getEnvironmentsListCopy() { |
| return this.environments.map(env => JSON.parse(JSON.stringify(env))); |
| } |
| |
| private getDefaultFilterConfiguration(): void { |
| const data: Exploratory[] = this.environments; |
| const shapes = [], statuses = [], resources = []; |
| |
| data.filter(elem => elem.exploratory.map((item: any) => { |
| if (shapes.indexOf(item.shape) === -1) shapes.push(item.shape); |
| if (statuses.indexOf(item.status) === -1) statuses.push(item.status); |
| statuses.sort(SortUtils.statusSort); |
| |
| item.resources.map((resource: any) => { |
| if (resources.indexOf(resource.status) === -1) resources.push(resource.status); |
| resources.sort(SortUtils.statusSort); |
| }); |
| })); |
| |
| this.filterConfiguration = new FilterConfigurationModel('', statuses, shapes, resources, '', ''); |
| } |
| |
| public applyFilter_btnClick(config: FilterConfigurationModel): void { |
| |
| let filteredData = this.getEnvironmentsListCopy(); |
| |
| const containsStatus = (list, selectedItems) => { |
| return list.filter((item: any) => { if (selectedItems.indexOf(item.status) !== -1) return item; }); |
| }; |
| |
| if (filteredData.length) this.filtering = true; |
| if (config) { |
| this.activeProject = config.project; |
| filteredData = filteredData |
| .filter(project => config.project ? project.project === config.project : project) |
| .filter(project => { |
| |
| project.exploratory = project.exploratory.filter(item => { |
| |
| const isName = item.name.toLowerCase().indexOf(config.name.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 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.length === 0 && config.type === 'active' || |
| modifiedResources.length >= 0 && config.resources.length > 0 && config.type === 'active') { |
| item.resources = modifiedResources; |
| isResources = true; |
| } |
| |
| return isName && isStatus && isShape && isResources; |
| }); |
| return project.exploratory.length > 0; |
| }); |
| |
| this.updateUserPreferences(config); |
| } |
| |
| let failedNotebooks = NotebookModel.notebook(this.getEnvironmentsListCopy()); |
| failedNotebooks = SortUtils.flatDeep(failedNotebooks, 1).filter(notebook => notebook.status === 'failed'); |
| if (this.filteredEnvironments.length && this.activeFiltering) { |
| let creatingNotebook = NotebookModel.notebook(this.filteredEnvironments); |
| creatingNotebook = SortUtils.flatDeep(creatingNotebook, 1).filter(resourse => resourse.status === 'creating'); |
| const fail = failedNotebooks |
| .filter(v => creatingNotebook |
| .some(create => create.project === v.project && create.exploratory === v.exploratory && create.resource === v.resource)); |
| if (fail.length) { |
| this.toastr.error('Creating notebook failed!', 'Oops!'); |
| } |
| } |
| |
| let failedResource = ComputationModel.computationRes(this.getEnvironmentsListCopy()); |
| failedResource = SortUtils.flatDeep(failedResource, 2).filter(resourse => resourse.status === 'failed'); |
| if (this.filteredEnvironments.length && this.activeFiltering) { |
| let creatingResource = ComputationModel.computationRes(this.filteredEnvironments); |
| creatingResource = SortUtils.flatDeep(creatingResource, 2).filter(resourse => resourse.status === 'creating'); |
| const fail = failedResource |
| .filter(v => creatingResource |
| .some(create => create.project === v.project && create.exploratory === v.exploratory && create.resource === v.resource)); |
| if (fail.length) { |
| this.toastr.error('Creating computation resource failed!', 'Oops!'); |
| } |
| } |
| this.filteredEnvironments = filteredData; |
| } |
| |
| private modifyGrid(): void { |
| this.displayedColumns = this.displayedColumns.filter(el => el !== 'cost'); |
| this.displayedFilterColumns = this.displayedFilterColumns.filter(el => el !== 'cost-filter'); |
| } |
| |
| private aliveStatuses(config): void { |
| for (const index in this.filterConfiguration) { |
| if (config[index] && config[index] instanceof Array) |
| config[index] = config[index].filter(item => this.filterConfiguration[index].includes(item)); |
| } |
| return config; |
| } |
| |
| isActiveFilter(filterConfig): void { |
| this.activeFiltering = false; |
| |
| for (const index in filterConfig) |
| if (filterConfig[index].length) this.activeFiltering = true; |
| } |
| |
| private getUserPreferences(): void { |
| this.userResourceService.getUserPreferences() |
| .subscribe((result: FilterConfigurationModel) => { |
| if (result) { |
| this.isActiveFilter(result); |
| this.filterForm = this.loadUserPreferences(result.type ? this.filterActiveInstances() : this.aliveStatuses(result)); |
| } |
| this.applyFilter_btnClick(result || this.filterForm); |
| }, () => this.applyFilter_btnClick(null)); |
| } |
| |
| private loadUserPreferences(config): FilterConfigurationModel { |
| return new FilterConfigurationModel(config.name, config.statuses, config.shapes, config.resources, config.type, config.project); |
| } |
| |
| private updateUserPreferences(filterConfiguration: FilterConfigurationModel): void { |
| this.userResourceService.updateUserPreferences(filterConfiguration) |
| .subscribe(() => { }, |
| (error) => console.log('UPDATE USER PREFERENCES ERROR ', error)); |
| } |
| |
| private logAction(name) { |
| this.auditService.sendDataToAudit({ |
| resource_name: name, info: `Open terminal, requested for notebook`, type: 'WEB_TERMINAL' |
| }).subscribe(); |
| } |
| |
| public trackBy(index, item) { |
| return null; |
| } |
| |
| } |