Show job nodes info in job table.

This commit is contained in:
Jingjing Li 2019-02-18 18:00:46 +08:00
Родитель 1dcbcff6ee
Коммит 2fa1bbbe64
17 изменённых файлов: 339 добавлений и 47 удалений

Просмотреть файл

@ -9,24 +9,27 @@
<div class="list-container">
<div class="list-header">
<div class="job-id" [ngStyle]="getColumnOrder('id')">
<div class="header job-id" [ngStyle]="getColumnOrder('id')">
ID
</div>
<div class="job-created" [ngStyle]="getColumnOrder('createdAt')">
<div class="header job-created" [ngStyle]="getColumnOrder('createdAt')">
Created
</div>
<div class="job-command" [ngStyle]="getColumnOrder('command')">
<div class="header job-command" [ngStyle]="getColumnOrder('command')">
Command
</div>
<div class="job-state" [ngStyle]="getColumnOrder('state')">
<div class="header job-state" [ngStyle]="getColumnOrder('state')">
State
</div>
<div class="job-progress" [ngStyle]="getColumnOrder('progress')">
<div class="header job-progress" [ngStyle]="getColumnOrder('progress')">
Progress
</div>
<div class="job-updated" [ngStyle]="getColumnOrder('updatedAt')">
<div class="header job-updated" [ngStyle]="getColumnOrder('updatedAt')">
Last Changed
</div>
<div class="header job-nodes" [ngStyle]="getColumnOrder('nodes')">
Nodes Number
</div>
</div>
<div class="list-content">
<cdk-virtual-scroll-viewport itemSize="40" #content class="list-content" (scrolledIndexChange)="indexChanged($event)">
@ -60,6 +63,12 @@
<i class="material-icons cell-icon">access_alarm</i>
<div class="cell-text"> {{job.updatedAt | date:'yyyy-MM-dd HH:mm:ss'}} </div>
</div>
<div class="icon-cell job-nodes" [ngStyle]="getColumnOrder('nodes')" (click)="getTargetNodes(job.id,job.targetNodes)"
[ngClass]="{'active-job': selectedJobId == job.id}">
<i class="material-icons cell-icon">devices</i>
<div class="cell-text"> {{job.targetNodesNum}} </div>
</div>
</div>
</cdk-virtual-scroll-viewport>
@ -72,3 +81,14 @@
</div>
<app-loading-progress-bar [loadFinished]="loadFinished" [hidden]="!loading || !scrolled" class="virtual-scroll-loading"></app-loading-progress-bar>
<app-content-window side="right" [title]="windowTitle" width="20" *ngIf="showTargetNodes" (showWnd)="onShowWnd($event)">
<ng-template #wndContent>
<div class="nodes" #nodes>
<div class="node icon-cell" *ngFor="let node of targetNodes">
<i class="material-icons cell-icon">desktop_windows</i>
<div class="cell-text">{{node}}</div>
</div>
</div>
</ng-template>
</app-content-window>

Просмотреть файл

@ -37,4 +37,41 @@
.job-created,
.job-state {
flex: 0.4;
}
}
.job-nodes {
flex: 0.3;
&:hover {
cursor: pointer;
}
.cell-text {
text-decoration-color: $color;
text-decoration-line: underline;
text-decoration-style: solid;
}
}
.active-job {
color: $color;
}
.nodes {
overflow-y: auto;
height: 85vh;
.node {
@include display-flex(center);
height: 25px;
font-size: 14px;
.cell-icon {
font-size: 1em;
}
&:hover {
color: $color;
}
}
}

Просмотреть файл

@ -11,6 +11,7 @@ import { ResultListComponent } from './result-list.component';
import { TableDataService } from '../../services/table-data/table-data.service';
import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { animationFrameScheduler } from 'rxjs';
import { Router, ActivatedRoute } from '@angular/router';
@Directive({
selector: '[routerLink]',
@ -25,15 +26,8 @@ class RouterLinkDirectiveStub {
}
}
// @Directive({
// selector: '[appWindowScroll]',
// })
// class WindowScrollDirectiveStub {
// @Input() dataLength: number;
// @Input() pageSize: number;
// @Output() scrollEvent = new EventEmitter();
// }
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);
const activatedRouteSpy = jasmine.createSpyObj('ActivatedRoute', ['']);
class ApiServiceStub {
static results = [
@ -105,7 +99,9 @@ fdescribe('ClusrunResultListComponent', () => {
{ provide: ApiService, useClass: ApiServiceStub },
{ provide: JobStateService, useClass: JobStateServiceStub },
{ provide: TableSettingsService, useValue: tableSettingsStub },
{ provide: TableDataService, useClass: TableDataServiceStub }
{ provide: TableDataService, useClass: TableDataServiceStub },
{ provide: Router, useValue: routerSpy },
{ provide: ActivatedRoute, useValue: activatedRouteSpy }
],
schemas: [NO_ERRORS_SCHEMA]
})

Просмотреть файл

@ -1,4 +1,4 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { MatDialog } from '@angular/material';
import { TableOptionComponent } from '../../widgets/table-option/table-option.component';
import { ApiService, Loop } from '../../services/api.service';
@ -7,6 +7,8 @@ import { JobStateService } from '../../services/job-state/job-state.service';
import { TableDataService } from '../../services/table-data/table-data.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { VirtualScrollService } from '../../services/virtual-scroll/virtual-scroll.service';
import { Router, ActivatedRoute } from '@angular/router';
import { ListJob } from '../../models/diagnostics/list-job';
@Component({
selector: 'app-result-list',
@ -15,10 +17,12 @@ import { VirtualScrollService } from '../../services/virtual-scroll/virtual-scro
})
export class ResultListComponent implements OnInit {
@ViewChild('content') cdkVirtualScrollViewport: CdkVirtualScrollViewport;
@ViewChild('nodes') nodes: ElementRef;
public dataSource = [];
static customizableColumns = [
{ name: 'createdAt', displayed: true, displayName: 'Created' },
{ name: 'nodes', display: true, displayName: 'Nodes' },
{ name: 'command', displayed: true, displayName: 'Command' },
{ name: 'state', displayed: true, displayName: 'State' },
{ name: 'progress', displayed: true, displayName: 'Progress' },
@ -47,8 +51,15 @@ export class ResultListComponent implements OnInit {
public empty = true;
private endId = -1;
public targetNodes: Array<ListJob>;
public showTargetNodes = false;
public selectedJobId = -1;
public windowTitle: string;
constructor(
private api: ApiService,
private router: Router,
private route: ActivatedRoute,
private jobStateService: JobStateService,
private tableDataService: TableDataService,
private dialog: MatDialog,
@ -145,6 +156,27 @@ export class ResultListComponent implements OnInit {
}
}
goDetailPage(id) {
this.router.navigate(['.', `${id}`], { relativeTo: this.route });
}
getTargetNodes(id, nodes) {
this.showTargetNodes = true;
if (this.nodes) {
this.nodes.nativeElement.scrollTop = 0;
}
this.selectedJobId = id;
this.windowTitle = `${nodes.length} Nodes`;
this.targetNodes = nodes;
}
onShowWnd(condition: boolean) {
this.showTargetNodes = condition;
if (!condition) {
this.selectedJobId = -1;
}
}
indexChanged($event) {
let result = this.virtualScrollService.indexChangedCalc(this.maxPageSize, this.pivot, this.cdkVirtualScrollViewport, this.dataSource, this.lastScrolled, this.startIndex);
this.pivot = result.pivot;

Просмотреть файл

@ -1,18 +1,18 @@
<div class="list-container">
<div class="list-header">
<div class="task-id" [ngStyle]="getColumnOrder('id')">
<div class="header task-id" [ngStyle]="getColumnOrder('id')">
ID
</div>
<div class="task-node" [ngStyle]="getColumnOrder('node')">
<div class="header task-node" [ngStyle]="getColumnOrder('node')">
Allocated Node
</div>
<div class="task-state" [ngStyle]="getColumnOrder('state')">
<div class="header task-state" [ngStyle]="getColumnOrder('state')">
State
</div>
<div class="task-remark" [ngStyle]="getColumnOrder('remark')">
<div class="header task-remark" [ngStyle]="getColumnOrder('remark')">
Remark
</div>
<div class="task-detail" [ngStyle]="getColumnOrder('detail')">
<div class="header task-detail" [ngStyle]="getColumnOrder('detail')">
Detail
</div>
</div>

Просмотреть файл

@ -9,35 +9,38 @@
<div class="list-container">
<div class="list-header">
<div class="job-id" [ngStyle]="getColumnOrder('id')">
<div class="header job-id" [ngStyle]="getColumnOrder('id')">
ID
</div>
<div class="job-created" [ngStyle]="getColumnOrder('createdAt')">
<div class="header job-created" [ngStyle]="getColumnOrder('createdAt')">
Created
</div>
<div class="job-category" [ngStyle]="getColumnOrder('category')">
<div class="header job-category" [ngStyle]="getColumnOrder('category')">
Category
</div>
<div class="job-item" [ngStyle]="getColumnOrder('item')">
<div class="header job-item" [ngStyle]="getColumnOrder('item')">
Item
</div>
<div class="job-name" [ngStyle]="getColumnOrder('name')">
<div class="header job-name" [ngStyle]="getColumnOrder('name')">
Name
</div>
<div class="job-state" [ngStyle]="getColumnOrder('state')">
<div class="header job-state" [ngStyle]="getColumnOrder('state')">
State
</div>
<div class="job-progress" [ngStyle]="getColumnOrder('progress')">
<div class="header job-progress" [ngStyle]="getColumnOrder('progress')">
Progress
</div>
<div class="job-updated" [ngStyle]="getColumnOrder('updatedAt')">
<div class="header job-updated" [ngStyle]="getColumnOrder('updatedAt')">
Last Changed
</div>
<div class="header job-nodes" [ngStyle]="getColumnOrder('nodes')">
Nodes Number
</div>
</div>
<div class="list-content">
<cdk-virtual-scroll-viewport itemSize="40" #content class="list-content" (scrolledIndexChange)="indexChanged($event)">
<div *cdkVirtualFor="let job of dataSource; templateCacheSize: 0; trackBy: trackByFn.bind(this)" class="list-item">
<div class="icon-cell job-id" [ngStyle]="getColumnOrder('id')">
<div class="icon-cell job-id" [ngStyle]="getColumnOrder('id')" (click)="goDetailPage(job.id)">
<i class="material-icons cell-icon color-icon">local_hospital</i>
<a [routerLink]="job.id" class="cell-text">{{job.id}}</a>
</div>
@ -73,6 +76,12 @@
<i class="material-icons cell-icon">access_alarm</i>
<div class="cell-text"> {{job.updatedAt | date:'yyyy-MM-dd HH:mm:ss'}} </div>
</div>
<div class="icon-cell job-nodes" [ngStyle]="getColumnOrder('nodes')" (click)="getTargetNodes(job.id,job.targetNodes)"
[ngClass]="{'active-job': selectedJobId == job.id}">
<i class="material-icons cell-icon">devices</i>
<div class="cell-text"> {{job.targetNodesNum}} </div>
</div>
</div>
</cdk-virtual-scroll-viewport>
@ -85,3 +94,14 @@
</div>
<app-loading-progress-bar [loadFinished]="loadFinished" [hidden]="!loading || !scrolled" class="virtual-scroll-loading"></app-loading-progress-bar>
<app-content-window side="right" [title]="windowTitle" width="20" *ngIf="showTargetNodes" (showWnd)="onShowWnd($event)" #contentWnd>
<ng-template #wndContent>
<div class="nodes" #nodes>
<div class="node icon-cell" *ngFor="let node of targetNodes">
<i class="material-icons cell-icon">desktop_windows</i>
<div class="cell-text">{{node}}</div>
</div>
</div>
</ng-template>
</app-content-window>

Просмотреть файл

@ -1,35 +1,77 @@
@import "../../stylesheets/mixin.scss";
@import "../../stylesheets/table.scss";
.list-item:hover {
cursor: text;
}
.id:hover {
cursor: pointer;
cursor: pointer;
}
.job-id {
flex: 0.2;
.icon-cell {
color: #3f51b5;
}
flex: 0.2;
&:hover {
cursor: pointer;
}
}
.job-progress {
flex: 0.8;
flex: 0.8;
}
.job-category,
.job-item {
flex: 0.5;
flex: 0.5;
}
.job-name {
flex: 0.7;
flex: 0.7;
}
.job-created,
.job-updated {
flex: 0.5;
flex: 0.5;
}
.job-state {
flex: 0.3;
}
flex: 0.3;
}
.job-nodes {
flex: 0.3;
&:hover {
cursor: pointer;
}
.cell-text {
text-decoration-color: $color;
text-decoration-line: underline;
text-decoration-style: solid;
}
}
.active-job {
color: $color;
}
.nodes {
overflow-y: auto;
height: 85vh;
.node {
@include display-flex(center);
height: 25px;
font-size: 14px;
.cell-icon {
font-size: 1em;
}
&:hover {
color: $color;
}
}
}

Просмотреть файл

@ -1,5 +1,5 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, Directive, Input, Output, EventEmitter, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { Directive, Input, Output, EventEmitter, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { of } from 'rxjs/observable/of';
import { FormsModule } from '@angular/forms';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@ -10,6 +10,7 @@ import { JobStateService } from '../../services/job-state/job-state.service';
import { ResultListComponent } from './result-list.component';
import { TableDataService } from '../../services/table-data/table-data.service';
import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { Router, ActivatedRoute } from '@angular/router';
@Directive({
selector: '[routerLink]',
@ -24,6 +25,9 @@ class RouterLinkDirectiveStub {
}
}
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);
const activatedRouteSpy = jasmine.createSpyObj('ActivatedRoute', ['']);
@Directive({
selector: '[appWindowScroll]',
})
@ -84,7 +88,9 @@ fdescribe('DiagResultListComponent', () => {
{ provide: ApiService, useClass: ApiServiceStub },
{ provide: JobStateService, useClass: JobStateServiceStub },
{ provide: TableSettingsService, useValue: tableSettingsStub },
{ provide: TableDataService, useClass: TableDataServiceStub }
{ provide: TableDataService, useClass: TableDataServiceStub },
{ provide: Router, useValue: routerSpy },
{ provide: ActivatedRoute, useValue: activatedRouteSpy }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

Просмотреть файл

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { MatDialog } from '@angular/material';
import { ApiService, Loop } from '../../services/api.service';
import { TableOptionComponent } from '../../widgets/table-option/table-option.component';
@ -7,6 +7,8 @@ import { JobStateService } from '../../services/job-state/job-state.service';
import { TableDataService } from '../../services/table-data/table-data.service';
import { VirtualScrollService } from '../../services/virtual-scroll/virtual-scroll.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { Router, ActivatedRoute } from '@angular/router';
import { ListJob } from '../../models/diagnostics/list-job';
@Component({
selector: 'diagnostics-results',
@ -15,9 +17,11 @@ import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
})
export class ResultListComponent implements OnInit, OnDestroy {
@ViewChild('content') cdkVirtualScrollViewport: CdkVirtualScrollViewport;
@ViewChild('nodes') nodes: ElementRef;
static customizableColumns = [
{ name: 'createdAt', displayed: true, displayName: 'Created' },
{ name: 'nodes', display: true, displayName: 'Nodes' },
{ name: 'category', displayed: true, displayName: 'Category' },
{ name: 'item', displayed: true, displayName: 'Item' },
{ name: 'name', displayed: true, displayName: 'Test Name' },
@ -48,9 +52,15 @@ export class ResultListComponent implements OnInit, OnDestroy {
public empty = true;
private endId = -1;
public targetNodes: Array<ListJob>;
public showTargetNodes = false;
public selectedJobId = -1;
public windowTitle: string;
constructor(
private api: ApiService,
private router: Router,
private route: ActivatedRoute,
private jobStateService: JobStateService,
private tableDataService: TableDataService,
private settings: TableSettingsService,
@ -149,6 +159,26 @@ export class ResultListComponent implements OnInit, OnDestroy {
}
}
goDetailPage(id) {
this.router.navigate(['.', `${id}`], { relativeTo: this.route });
}
getTargetNodes(id, nodes) {
this.showTargetNodes = true;
if (this.nodes) {
this.nodes.nativeElement.scrollTop = 0;
}
this.selectedJobId = id;
this.windowTitle = `${nodes.length} Nodes`;
this.targetNodes = nodes;
}
onShowWnd(condition: boolean) {
this.showTargetNodes = condition;
if (!condition) {
this.selectedJobId = -1;
}
}
indexChanged($event) {
let result = this.virtualScrollService.indexChangedCalc(this.maxPageSize, this.pivot, this.cdkVirtualScrollViewport, this.dataSource, this.lastScrolled, this.startIndex);

Просмотреть файл

@ -5,4 +5,6 @@ export class ClusrunJob {
createdAt: number;
updatedAt: number;
progress: number;
targetNodes: Array<String>;
targetNodesNum: number;
}

Просмотреть файл

@ -9,4 +9,6 @@ export class ListJob {
state: string;
progress: number;
createdAt: string;
targetNodes: Array<String>;
targetNodesNum: number;
}

Просмотреть файл

@ -182,6 +182,8 @@ export class CommandApi extends Resource<CommandResult> {
state: item.state,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
targetNodes: item.targetNodes,
targetNodesNum: item.targetNodes.length,
progress: item.progress
} as ClusrunJob
});
@ -412,6 +414,8 @@ export class DiagApi extends Resource<any> {
progress: item.progress,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
targetNodes: item.targetNodes,
targetNodesNum: item.targetNodes.length,
diagnosticTest: {
name: item.diagnosticTest.name,
category: item.diagnosticTest.category

Просмотреть файл

@ -0,0 +1,11 @@
<div [ngClass]="{ 'right-window-area' : side == 'right', 'left-window-area' : side =='left' }" [ngStyle]="{'width': width+'vw'}">
<div class="window-header">
<h3 class="title">
{{title}}
</h3>
<div class="close-btn" (click)="hideWnd()">
<i class="material-icons">close</i>
</div>
</div>
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>

Просмотреть файл

@ -0,0 +1,31 @@
@import "../../stylesheets/mixin.scss";
.right-window-area {
position: fixed;
right: 0;
top: 64px;
bottom: 0;
z-index: 1000;
background: #ffffff;
@include box-s(-5px, 0px, 10px, #eeeeee);
padding: 10px;
}
.left-window-area {
position: fixed;
left: 0;
top: 64px;
bottom: 0;
z-index: 1000;
background: #ffffff;
@include box-s(0px, -5px, 10px, #eeeeee);
padding: 10px;
}
.window-header {
@include display-flex(center, space-between);
.close-btn {
cursor: pointer;
}
}

Просмотреть файл

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ContentWindowComponent } from './content-window.component';
fdescribe('ContentWindowComponent', () => {
let component: ContentWindowComponent;
let fixture: ComponentFixture<ContentWindowComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ContentWindowComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContentWindowComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Просмотреть файл

@ -0,0 +1,32 @@
import { Component, OnInit, Input, TemplateRef, ContentChild, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-content-window',
templateUrl: './content-window.component.html',
styleUrls: ['./content-window.component.scss']
})
export class ContentWindowComponent implements OnInit {
@Input()
side = 'right';
@Input()
title = '';
@Input()
width: number;
@Output()
showWnd = new EventEmitter<boolean>();
@ContentChild('wndContent')
contentTemplate: TemplateRef<any>;
constructor() { }
ngOnInit() {
}
hideWnd() {
this.showWnd.emit(false);
}
}

Просмотреть файл

@ -8,13 +8,15 @@ import { EventListComponent } from './event-list/event-list.component';
import { ScheduledEventsComponent } from './scheduled-events/scheduled-events.component'
import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component';
import { DragulaModule } from 'ng2-dragula';
import { ContentWindowComponent } from './content-window/content-window.component';
const components = [
BackButtonComponent,
TableOptionComponent,
EventListComponent,
ScheduledEventsComponent,
ConfirmDialogComponent
ConfirmDialogComponent,
ContentWindowComponent
];
@NgModule({