This is an automated email from the ASF dual-hosted git repository. hshpak pushed a commit to branch fix/DATALAB-2842/button-visibility-of-edge-node-recreation in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git
commit f5e206073610ae80cff54dba8610061828e9e2b6 Author: Hennadii_Shpak <[email protected]> AuthorDate: Wed Jun 22 13:46:14 2022 +0300 [DATALAB-2842] added logic by disable recreate button --- .../create-odahu-cluster.component.ts | 2 +- .../webapp/src/app/administration/project/index.ts | 8 +- .../administration/project/project-data.service.ts | 2 +- .../project/project-form/project-form.component.ts | 8 +- .../project-list/project-list.component.html | 31 +++-- .../project-list/project-list.component.scss | 19 ++- .../project/project-list/project-list.component.ts | 141 ++++++++++++--------- .../administration/project/project.component.ts | 29 +---- .../project/project.config.ts} | 16 +-- .../project/project.model.ts} | 29 +++-- .../webapp/src/app/core/directives/index.ts | 8 +- .../directives/is-endpoint-active.directive.ts | 60 +++++++++ .../core/util/{index.ts => checkEndpointList.ts} | 15 +-- .../webapp/src/app/core/util/checkUtils.ts | 2 +- .../resources/webapp/src/app/core/util/index.ts | 1 + .../create-environment.component.ts | 15 ++- .../resources-grid/resources-grid.component.ts | 2 +- .../notification-dialog.component.ts | 3 +- .../src/app/shared/navbar/navbar.component.html | 4 +- 19 files changed, 248 insertions(+), 147 deletions(-) diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/odahu/create-odahu-claster/create-odahu-cluster.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/odahu/create-odahu-claster/create-odahu-cluster.component.ts index a4f8360fa..e33e2e97e 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/odahu/create-odahu-claster/create-odahu-cluster.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/odahu/create-odahu-claster/create-odahu-cluster.component.ts @@ -22,11 +22,11 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; -import { Project } from '../../project/project.component'; import { ProjectService, OdahuDeploymentService } from '../../../core/services'; import { DICTIONARY } from '../../../../dictionary/global.dictionary'; import {CheckUtils, PATTERNS} from '../../../core/util'; +import { Project } from '../../project/project.model'; @Component({ diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/index.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/index.ts index 8e57f0fb0..d1fd9d7ff 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/index.ts @@ -23,15 +23,16 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MaterialModule } from '../../shared/material.module'; import { FormControlsModule } from '../../shared/form-controls'; -import { UnderscorelessPipeModule } from '../../core/pipes/underscoreless-pipe'; import { ProjectFormComponent } from './project-form/project-form.component'; import { ProjectListComponent } from './project-list/project-list.component'; import { ProjectComponent, EditProjectComponent } from './project.component'; import { ProjectDataService } from './project-data.service'; -import {BubbleModule} from "../../shared/bubble"; import {InformMessageModule} from '../../shared/inform-message'; +import { DirectivesModule } from '../../core/directives'; +import { BubbleModule } from '../../shared'; +import { UnderscorelessPipeModule } from '../../core/pipes'; @NgModule({ imports: [ @@ -42,7 +43,8 @@ import {InformMessageModule} from '../../shared/inform-message'; FormControlsModule, UnderscorelessPipeModule, BubbleModule, - InformMessageModule + InformMessageModule, + DirectivesModule ], declarations: [ProjectComponent, EditProjectComponent, ProjectFormComponent, ProjectListComponent], entryComponents: [EditProjectComponent], diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-data.service.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-data.service.ts index 0cb20d26b..ad9e5c380 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-data.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-data.service.ts @@ -22,7 +22,7 @@ import { BehaviorSubject, of } from 'rxjs'; import { mergeMap} from 'rxjs/operators'; import { ProjectService, EndpointService } from '../../core/services'; -import { Project } from './project.component'; +import { Project } from './project.model'; @Injectable() export class ProjectDataService { diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-form/project-form.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-form/project-form.component.ts index b5cd1b84d..d6fe75cde 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-form/project-form.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-form/project-form.component.ts @@ -27,9 +27,9 @@ import { Subscription } from 'rxjs'; import { ProjectService, RolesGroupsService, EndpointService, UserAccessKeyService } from '../../../core/services'; import { ProjectDataService } from '../project-data.service'; import { CheckUtils, FileUtils, PATTERNS } from '../../../core/util'; -import { Project } from '../project.component'; import { DICTIONARY } from '../../../../dictionary/global.dictionary'; import {ConfirmationDialogComponent} from '../../../shared/modal-dialog/confirmation-dialog'; +import { Project } from '../project.model'; export interface GenerateKey { privateKey: string; publicKey: string; } @@ -88,7 +88,7 @@ export class ProjectFormComponent implements OnInit { () => { this.toastr.success('Project updated successfully!', 'Success!'); this.update.emit(); - }, + }, error => this.toastr.error(error.message || 'Project update failed!', 'Oops!') ); } @@ -96,7 +96,7 @@ export class ProjectFormComponent implements OnInit { public confirm(data) { if (this.item) { const deletedGroups = this.item.groups.filter((v) => !(this.projectForm.value.groups.includes(v))); - + if (deletedGroups.length) { this.dialog.open(ConfirmationDialogComponent, { data: {notebook: deletedGroups, type: 5, manageAction: true}, panelClass: 'modal-md' @@ -170,7 +170,7 @@ export class ProjectFormComponent implements OnInit { public selectOptions(list, key, select?) { - const filter = key === 'endpoints' ? list.filter(el => el.status === 'ACTIVE').map(el => el.name) : list + const filter = key === 'endpoints' ? list.filter(el => el.status === 'ACTIVE').map(el => el.name) : list; this.projectForm.controls[key].setValue(select ? filter : []); } diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.html index bf4c8f739..ce81d953a 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.html @@ -28,7 +28,7 @@ <td mat-cell *matCellDef="let element" class="groups"> <div class="mat-chip-list-wrap scrolling"> <mat-chip-list> - <mat-chip + <mat-chip *ngFor="let group of element.groups" [matTooltip]="group" matTooltipPosition="above" @@ -45,7 +45,7 @@ <th mat-header-cell *matHeaderCellDef class="endpoints"> <span class="label-endpoint"> Endpoint </span> <span class="label-endpoint-status"> Endpoint status </span> - <span class="label-status">Edge node status </span> + <span class="label-status">Edge node status</span> </th> <td mat-cell *matCellDef="let element" class="source endpoints"> <div *ngIf="!element.endpoints?.length; else list"> @@ -63,9 +63,9 @@ {{ (endpoint.endpointStatus | titlecase) || 'N/A'}} </span> </div> - + <span class="status resource-status" [ngClass]="endpoint?.status.toLowerCase() || ''"> - {{ endpoint?.status.toLowerCase() }} + {{ endpoint?.status | titlecase }} </span> </div> </ng-template> @@ -93,11 +93,24 @@ Stop edge node </a> </li> - <li class="project-seting-item " *ngIf="element.areTerminatedNode && isEndpointAvailable" (click)="openEdgeDialog('recreate', element)"> - <i class="material-icons">refresh</i> - <a class="action"> - Recreate edge node - </a> + <li + *ngIf="(element.areTerminatedNode || element.areStoppedNode) && isEndpointAvailable" + (click)="openEdgeDialog('recreate', element)" + + > + <button class="project-setting-button" + #recreateBtn + datalabIsEndpointActive + [endpointList]="element.endpoints" + [matTooltip]="'Unable to recreate edge node if endpoint status is not active.'" + matTooltipPosition="above" + [matTooltipDisabled]="!isRecreateBtnDisabled(element.endpoints)" + > + <i class="material-icons">refresh</i> + <a class="action" > + Recreate edge node + </a> + </button> </li> <li class="project-seting-item " *ngIf="element.areStoppedNode || element.areRunningNode" (click)="openEdgeDialog('terminate', element)"> <i class="material-icons">phonelink_off</i> diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.scss b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.scss index 836b822f6..0abea7e84 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.scss +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.scss @@ -138,7 +138,8 @@ td.settings { } } -.project-seting-item { +.project-seting-item, +.project-setting-button{ display: flex; align-items: center; padding: 10px; @@ -157,6 +158,18 @@ td.settings { } } +.project-setting-button { + width: 100%; + background-color: transparent; + border: none; +} + +.disabled-button { + color: rgba(0,0,0,0.26); + background-color: rgba(0,0,0,0.12); + cursor: not-allowed; +} + .material-icons { font-size: 18px; padding-top: 1px; @@ -171,11 +184,11 @@ td.settings { } .actions-list { - padding: 10px 15px; + padding: 10px 0px; } .mat-chip { max-width: 200px !important; white-space: nowrap; display: inline-block; -} \ No newline at end of file +} diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts index ca2c331a4..0086cb918 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts @@ -17,33 +17,37 @@ * under the License. */ -import { Component, OnInit, Output, EventEmitter, OnDestroy, Inject, Input } from '@angular/core'; +import { Component, OnInit, Output, EventEmitter, OnDestroy, Inject, Input, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; import { ToastrService } from 'ngx-toastr'; import { MatTableDataSource } from '@angular/material/table'; import { Subscription } from 'rxjs'; import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; import { ProjectDataService } from '../project-data.service'; -import { Project, Endpoint } from '../project.component'; import {ProgressBarService} from '../../../core/services/progress-bar.service'; import {EdgeActionDialogComponent} from '../../../shared/modal-dialog/edge-action-dialog'; import { EndpointService } from '../../../core/services'; +import { Endpoint, ModifiedEndpoint, Project } from '../project.model'; +import { checkEndpointList } from '../../../core/util'; +import { EndpointStatus } from '../project.config'; @Component({ selector: 'project-list', templateUrl: './project-list.component.html', styleUrls: ['./project-list.component.scss', '../../../resources/computational/computational-resources-list/computational-resources-list.component.scss'] }) -export class ProjectListComponent implements OnInit, OnDestroy { +export class ProjectListComponent implements OnInit, OnDestroy, AfterViewInit { + @Input() isProjectAdmin: boolean; + @Output() editItem: EventEmitter<{}> = new EventEmitter(); + @Output() toggleStatus: EventEmitter<{}> = new EventEmitter(); + + @ViewChild('recreateBtn') recreateBtn: ElementRef; displayedColumns: string[] = ['name', 'groups', 'endpoints', 'actions']; dataSource: Project[] | any = []; projectList: Project[]; isEndpointAvailable: boolean; - @Input() isProjectAdmin: boolean; - @Output() editItem: EventEmitter<{}> = new EventEmitter(); - @Output() toggleStatus: EventEmitter<{}> = new EventEmitter(); private subscriptions: Subscription = new Subscription(); constructor ( @@ -56,52 +60,23 @@ export class ProjectListComponent implements OnInit, OnDestroy { public dialog: MatDialog, ) { } - ngOnInit() { + ngOnInit(): void { this.getProjectList(); this.getEndpointList(); } - ngOnDestroy() { - this.subscriptions.unsubscribe(); - } + ngAfterViewInit(): void { - private getProjectList() { - this.progressBarService.startProgressBar(); - this.subscriptions.add(this.projectDataService._projects - .subscribe( - (value: Project[]) => { - this.projectList = value; - if (this.projectList) { - this.projectList.forEach(project => { - project.areRunningNode = this.areResoursesInStatuses(project.endpoints, ['RUNNING']); - project.areStoppedNode = this.areResoursesInStatuses(project.endpoints, ['STOPPED']); - project.areTerminatedNode = this.areResoursesInStatuses(project.endpoints, ['TERMINATED', 'FAILED']); - }); - } - if (value) this.dataSource = new MatTableDataSource(value); - this.progressBarService.stopProgressBar(); - }, - () => this.progressBarService.stopProgressBar() - ) - ); } - private getEndpointList() { - this.endpointService.getEndpointsData().subscribe( - (response: Endpoint[] | []) => { - this.isEndpointAvailable = this.checkIsEndpointAvailable(response); - } - ) - } - - private checkIsEndpointAvailable(data: Endpoint[] | []): boolean { - return data.length ? true : false; + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); } public showActiveInstances(): void { const filteredList = this.projectList.map(project => { project.endpoints = project.endpoints.filter((endpoint: Endpoint) => { - return endpoint.status !== 'TERMINATED' && endpoint.status !== 'TERMINATING' && endpoint.status !== 'FAILED' + return endpoint.status !== 'TERMINATED' && endpoint.status !== 'TERMINATING' && endpoint.status !== 'FAILED'; }); return project; }); @@ -113,37 +88,83 @@ export class ProjectListComponent implements OnInit, OnDestroy { this.editItem.emit(item); } - public openEdgeDialog(action, project) { - const endpoints = project.endpoints.filter(endpoint => { + public openEdgeDialog(action: string, project: Project) { + const endpoints = this.getFilteredEndpointList(action, project); + + if (action === 'terminate' && endpoints.length === 1) { + this.toggleStatus.emit({ project, endpoint: endpoints, action, oneEdge: true }); + } else { + this.dialog.open(EdgeActionDialogComponent, { data: { type: action, item: endpoints }, panelClass: 'modal-sm' }) + .afterClosed().subscribe( + endpoint => { + if (endpoint && endpoint.length) { + this.toggleStatus.emit({project, endpoint, action}); + } + }, + error => this.toastr.error(error.message || `Endpoint ${action} failed!`, 'Oops!') + ); + } + } + + public areResoursesInStatuses(resources, statuses: Array<string>) { + return resources.some(resource => statuses.some(status => resource.status === status)); + } + + isRecreateBtnDisabled(endpointList: ModifiedEndpoint[]): boolean { + return checkEndpointList(endpointList); + } + + private getFilteredEndpointList(action: string, project) { + return project.endpoints.filter(endpoint => { if (action === 'stop') { - return endpoint.status === 'RUNNING'; + return endpoint.status === EndpointStatus.running; } if (action === 'start') { - return endpoint.status === 'STOPPED'; + return endpoint.status === EndpointStatus.stopped; } if (action === 'terminate') { - return endpoint.status === 'RUNNING' || endpoint.status === 'STOPPED'; + return endpoint.status === EndpointStatus.running || endpoint.status === EndpointStatus.stopped; } if (action === 'recreate') { - return endpoint.status === 'TERMINATED' || endpoint.status === 'FAILED'; + return endpoint.status === EndpointStatus.terminated || endpoint.status === EndpointStatus.failed; } }); - if (action === 'terminate' && endpoints.length === 1) { - this.toggleStatus.emit({ project, endpoint: endpoints, action, oneEdge: true }); - } else { - this.dialog.open(EdgeActionDialogComponent, { data: { type: action, item: endpoints }, panelClass: 'modal-sm' }) - .afterClosed().subscribe( - endpoint => { - if (endpoint && endpoint.length) { - this.toggleStatus.emit({project, endpoint, action}); - } - }, - error => this.toastr.error(error.message || `Endpoint ${action} failed!`, 'Oops!') - ); + } + + private getProjectList() { + this.progressBarService.startProgressBar(); + this.subscriptions.add(this.projectDataService._projects + .subscribe( + (value: Project[]) => { + this.modifyProjectList(value); + if (value) this.dataSource = new MatTableDataSource(value); + this.progressBarService.stopProgressBar(); + }, + () => this.progressBarService.stopProgressBar() + ) + ); + } + + private modifyProjectList(value: Project[]): void { + this.projectList = value; + if (this.projectList) { + this.projectList.forEach(project => { + project.areRunningNode = this.areResoursesInStatuses(project.endpoints, [EndpointStatus.running]); + project.areStoppedNode = this.areResoursesInStatuses(project.endpoints, [EndpointStatus.stopped]); + project.areTerminatedNode = this.areResoursesInStatuses(project.endpoints, [EndpointStatus.terminated, EndpointStatus.failed]); + }); } } - public areResoursesInStatuses(resources, statuses: Array<string>) { - return resources.some(resource => statuses.some(status => resource.status === status)); + private getEndpointList() { + this.endpointService.getEndpointsData().subscribe( + (response: Endpoint[] | []) => { + this.isEndpointAvailable = this.checkIsEndpointAvailable(response); + } + ); + } + + private checkIsEndpointAvailable(data: Endpoint[] | []): boolean { + return data.length ? true : false; } } diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts index 1409c7b80..61e486590 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts @@ -27,23 +27,8 @@ import {HealthStatusService, ProjectService } from '../../core/services'; import { NotificationDialogComponent } from '../../shared/modal-dialog/notification-dialog'; import { ProjectListComponent } from './project-list/project-list.component'; import { EnvironmentsDataService } from '../management/management-data.service'; +import { Project } from './project.model'; -export interface Endpoint { - name: string; - status: string; - edgeInfo: any; -} - -export interface Project { - name: string; - endpoints: any; - tag: string; - groups: string[]; - shared_image_enabled?: boolean; - areStoppedNode?: boolean; - areTerminatedNode?: boolean; - areRunningNode?: boolean; -} @Component({ selector: 'datalab-project', @@ -118,8 +103,8 @@ export class ProjectComponent implements OnInit, OnDestroy { } public toggleStatus($event) { - const data = { - 'project_name': $event.project.name, + const data = { + 'project_name': $event.project.name, endpoint: $event.endpoint.map(endpoint => endpoint.name), 'edge_status': $event.endpoint.map(endpoint => endpoint.status)[0] }; @@ -129,11 +114,11 @@ export class ProjectComponent implements OnInit, OnDestroy { private toggleStatusRequest(data, action, isOnlyOneEdge?) { if ( action === 'terminate') { const projectsResources = this.resources - .filter(resource => resource.project === data.project_name && resource.resource_type !== "edge node"); + .filter(resource => resource.project === data.project_name && resource.resource_type !== 'edge node'); const activeProjectsResources = projectsResources?.length ? projectsResources .filter(expl => expl.status !== 'terminated' && expl.status !== 'terminating' && expl.status !== 'failed') : []; - + const termResources = data.endpoint.reduce((res, endp) => { res.push(...activeProjectsResources.filter(resource => resource.endpoint === endp)); return res; @@ -143,7 +128,7 @@ export class ProjectComponent implements OnInit, OnDestroy { this.edgeNodeAction(data, action); } else { this.dialog.open(NotificationDialogComponent, { data: { - type: 'terminateNode', + type: 'terminateNode', item: {action: data, resources: termResources} }, panelClass: 'modal-sm' }) .afterClosed().subscribe(result => { @@ -161,7 +146,7 @@ export class ProjectComponent implements OnInit, OnDestroy { () => { this.refreshGrid(); this.toastr.success(`Edge node ${this.toEndpointAction(action)} is in progress!`, 'Processing!'); - }, + }, error => { this.toastr.error(error.message, 'Oops!'); } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.config.ts similarity index 74% copy from services/self-service/src/main/resources/webapp/src/app/core/util/index.ts copy to services/self-service/src/main/resources/webapp/src/app/administration/project/project.config.ts index 8c04f80aa..fa333d082 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.config.ts @@ -17,12 +17,10 @@ * under the License. */ -export * from './http-status-codes'; -export * from './sortUtils'; -export * from './helpUtils'; -export * from './errorUtils'; -export * from './dateUtils'; -export * from './fileUtils'; -export * from './checkUtils'; -export * from './patterns'; -export * from './http-methods'; +export enum EndpointStatus { + terminated = 'TERMINATED', + terminating = 'TERMINATING', + failed = 'FAILED', + running = 'RUNNING', + stopped = 'STOPPED', +} diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.model.ts similarity index 66% copy from services/self-service/src/main/resources/webapp/src/app/core/util/index.ts copy to services/self-service/src/main/resources/webapp/src/app/administration/project/project.model.ts index 8c04f80aa..bfdc79030 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.model.ts @@ -17,12 +17,23 @@ * under the License. */ -export * from './http-status-codes'; -export * from './sortUtils'; -export * from './helpUtils'; -export * from './errorUtils'; -export * from './dateUtils'; -export * from './fileUtils'; -export * from './checkUtils'; -export * from './patterns'; -export * from './http-methods'; +export interface Endpoint { + name: string; + status: string; + edgeInfo: any; +} + +export interface ModifiedEndpoint extends Endpoint { + endpointStatus?: 'ACTIVE' | 'INACTIVE'; +} + +export interface Project { + name: string; + endpoints: any; + tag: string; + groups: string[]; + shared_image_enabled?: boolean; + areStoppedNode?: boolean; + areTerminatedNode?: boolean; + areRunningNode?: boolean; +} diff --git a/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts index 83a0eaee4..9f8dcb2b2 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts @@ -22,14 +22,12 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ClickOutsideDirective } from './click-outside.directive'; import { ScrollDirective } from './scrollTo.directive'; - -export * from './scrollTo.directive'; -export * from './click-outside.directive'; +import { IsEndpointsActiveDirective } from './is-endpoint-active.directive'; @NgModule({ imports: [CommonModule], - declarations: [ClickOutsideDirective, ScrollDirective], - exports: [ClickOutsideDirective, ScrollDirective] + declarations: [ClickOutsideDirective, ScrollDirective, IsEndpointsActiveDirective], + exports: [ClickOutsideDirective, ScrollDirective, IsEndpointsActiveDirective] }) export class DirectivesModule { } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts b/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts new file mode 100644 index 000000000..0f9db68bd --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts @@ -0,0 +1,60 @@ +/* + * 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 { Directive, ElementRef, HostListener, Input, OnInit, Renderer2 } from '@angular/core'; + +import { ModifiedEndpoint } from '../../administration/project/project.model'; +import { checkEndpointList } from '../util'; + + +@Directive({ + selector: '[datalabIsEndpointActive]' +}) +export class IsEndpointsActiveDirective implements OnInit { + @Input() endpointList: ModifiedEndpoint[]; + + private isButtonDisabled: boolean = false; + + constructor ( + private el: ElementRef, + private renderer: Renderer2 + ) { } + + ngOnInit(): void { + this.checkEndpointList(this.endpointList); + this.setStyle(); + } + + @HostListener('click', ['$event']) + onClick(event: Event): void { + if (this.isButtonDisabled) { + event.stopPropagation(); + } + } + + private setStyle() { + if (this.isButtonDisabled) { + this.renderer.addClass(this.el.nativeElement, 'disabled-button'); + } + } + + private checkEndpointList(endpointList: ModifiedEndpoint[]): void { + this.isButtonDisabled = checkEndpointList(endpointList); + } +} diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts similarity index 72% copy from services/self-service/src/main/resources/webapp/src/app/core/util/index.ts copy to services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts index 8c04f80aa..99f927ddc 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts @@ -17,12 +17,9 @@ * under the License. */ -export * from './http-status-codes'; -export * from './sortUtils'; -export * from './helpUtils'; -export * from './errorUtils'; -export * from './dateUtils'; -export * from './fileUtils'; -export * from './checkUtils'; -export * from './patterns'; -export * from './http-methods'; +import { ModifiedEndpoint } from '../../administration/project/project.model'; + +export const checkEndpointList = (endpointList: ModifiedEndpoint[]): boolean => { + return endpointList.every(({status, endpointStatus}) => (status === 'TERMINATED' || status === 'FAILED') + && endpointStatus === 'INACTIVE'); +}; diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/checkUtils.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/checkUtils.ts index d1e6f535e..ca3e5a2c2 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/util/checkUtils.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/util/checkUtils.ts @@ -28,7 +28,7 @@ export class CheckUtils { STOPPING: 'DISCONNECTING', STOPPED: 'DISCONNECTED' }; - + public static isJSON(str) { try { JSON.parse(str); diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts index 8c04f80aa..b00239022 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts @@ -26,3 +26,4 @@ export * from './fileUtils'; export * from './checkUtils'; export * from './patterns'; export * from './http-methods'; +export * from './checkEndpointList'; diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/create-environment/create-environment.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/create-environment/create-environment.component.ts index d0b107ea5..debc6515c 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/create-environment/create-environment.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/create-environment/create-environment.component.ts @@ -22,7 +22,6 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; -import { Project } from '../../../administration/project/project.component'; import { UserResourceService, ProjectService } from '../../../core/services'; import { CheckUtils, SortUtils, HTTP_STATUS_CODES, PATTERNS, HelpUtils } from '../../../core/util'; import { DICTIONARY } from '../../../../dictionary/global.dictionary'; @@ -30,6 +29,7 @@ import { CLUSTER_CONFIGURATION } from '../../computational/computational-resourc import { tap } from 'rxjs/operators'; import { timer } from 'rxjs'; import { TemplateName } from '../../../core/models'; +import { Project } from '../../../administration/project/project.model'; @Component({ selector: 'create-environment', @@ -181,7 +181,8 @@ export class ExploratoryEnvironmentCreateComponent implements OnInit { this.gpuTypes = template?.computationGPU ? HelpUtils.sortGpuTypes(template.computationGPU) : []; - if(template?.image === 'docker.datalab-tensor' /**|| template?.image === 'docker.datalab-jupyter-conda'|| template?.image === 'docker.datalab-jupyter-gpu' */|| template?.image === 'docker.datalab-deeplearning') { + // tslint:disable-next-line:max-line-length + if (template?.image === 'docker.datalab-tensor' /**|| template?.image === 'docker.datalab-jupyter-conda'|| template?.image === 'docker.datalab-jupyter-gpu' */|| template?.image === 'docker.datalab-deeplearning') { this.addGpuFields(); } } @@ -272,9 +273,9 @@ export class ExploratoryEnvironmentCreateComponent implements OnInit { } public setInstanceSize() { - const {image, computationGPU} = this.currentTemplate - if(image === this.templateName.jupyterJpu) { - this.createExploratoryForm.get('gpu_count').setValue(computationGPU[0]) + const {image, computationGPU} = this.currentTemplate; + if (image === this.templateName.jupyterJpu) { + this.createExploratoryForm.get('gpu_count').setValue(computationGPU[0]); } else { const controls = ['gpu_type', 'gpu_count']; controls.forEach(control => { @@ -329,9 +330,9 @@ export class ExploratoryEnvironmentCreateComponent implements OnInit { (res: any) => { this.images = res.filter(el => el.status === 'CREATED'); - if(this.selectedCloud === 'gcp' && this.currentTemplate.image === 'docker.datalab-deeplearning') { + if (this.selectedCloud === 'gcp' && this.currentTemplate.image === 'docker.datalab-deeplearning') { this.currentTemplate.exploratory_environment_images = this.currentTemplate.exploratory_environment_images.map(image => { - return {name: image['Image family'] ?? image.name, description: image['Description'] ?? image.description} + return {name: image['Image family'] ?? image.name, description: image['Description'] ?? image.description}; }); this.images.push(...this.currentTemplate.exploratory_environment_images); } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts index 28b79ecd8..4677f57d2 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts @@ -17,7 +17,6 @@ * under the License. */ -import { Project } from '../../administration/project/project.component'; import { Component, EventEmitter, @@ -47,6 +46,7 @@ import { NotebookModel } from '../exploratory/notebook.model'; import { AuditService } from '../../core/services/audit.service'; import { CompareUtils } from '../../core/util/compareUtils'; import { OdahuActionDialogComponent } from '../../shared/modal-dialog/odahu-action-dialog'; +import { Project } from '../../administration/project/project.model'; export interface SharedEndpoint { edge_node_ip: string; diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/notification-dialog.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/notification-dialog.component.ts index 2cdee43fc..4f532c0e8 100644 --- a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/notification-dialog.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/notification-dialog.component.ts @@ -19,7 +19,8 @@ import { Component, Inject } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import {Endpoint} from '../../../administration/project/project.component'; + +import { Endpoint } from '../../../administration/project/project.model'; @Component({ selector: 'notification-dialog', diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html index 4ec0819a6..8ae321ffe 100644 --- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html @@ -28,8 +28,8 @@ <span class="line"></span> </button> - <a [routerLink]="['/resources_list']" class="navbar-logo"> - <img src="assets/svg/logo.svg" alt=""> + <a [routerLink]="['/instances']" class="navbar-logo"> + <img src="assets/svg/logo.svg" alt="datalab"> </a> </div> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
