Modify tag detail page and fix label bugs

This commit is contained in:
pfh 2018-03-27 10:12:37 +08:00
parent cbcca015b0
commit 42fa690f90
19 changed files with 104 additions and 68 deletions

View File

@ -81,6 +81,7 @@ export class Configuration {
token_expiration: NumberValueItem;
cfg_expiration: NumberValueItem;
scan_all_policy: ComplexValueItem;
read_only: BoolValueItem;
public constructor() {
this.auth_mode = new StringValueItem("db_auth", true);
@ -116,5 +117,6 @@ export class Configuration {
daily_time: 0
}
}, true);
this.read_only = new BoolValueItem(false, true);
}
}

View File

@ -10,8 +10,8 @@ export const CREATE_EDIT_LABEL_STYLE: string = `
section{padding:.5rem 0;}
section> label{margin-left: 20px;}
.dropdown-menu{display:inline-block;width:166px; padding:6px;}
.dropdown-item{ display:inline-flex; margin:2px 4px;
.dropdown-menu {display:inline-block;width:166px; padding:6px;}
.dropdown-menu .dropdown-item{ display:inline-flex; margin:2px 4px;
display: inline-block;padding: 0px; width:30px;height:24px; text-align: center;line-height: 24px;}
.btnColor{
margin: 0 !important;

View File

@ -16,7 +16,7 @@ import {
Output,
EventEmitter,
OnDestroy,
Input, OnInit, ViewChild
Input, OnInit, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef
} from '@angular/core';
@ -27,16 +27,15 @@ import { CREATE_EDIT_LABEL_TEMPLATE } from './create-edit-label.component.html';
import {toPromise, clone, compareValue} from '../utils';
import {Subject} from "rxjs/Subject";
import {LabelService} from "../service/label.service";
import {ErrorHandler} from "../error-handler/error-handler";
import {NgForm} from "@angular/forms";
import {Subject} from "rxjs/Subject";
@Component({
selector: 'hbr-create-edit-label',
template: CREATE_EDIT_LABEL_TEMPLATE,
styles: [CREATE_EDIT_LABEL_STYLE]
styles: [CREATE_EDIT_LABEL_STYLE],
})
export class CreateEditLabelComponent implements OnInit, OnDestroy {
@ -46,12 +45,13 @@ export class CreateEditLabelComponent implements OnInit, OnDestroy {
labelModel: Label = this.initLabel();
labelId = 0;
nameChecker: Subject<string> = new Subject<string>();
checkOnGoing: boolean;
isLabelNameExist = false;
labelColor = ['#00ab9a', '#9da3db', '#be90d6', '#9b0d54', '#f52f22', '#747474', '#0095d3', '#f38b00', ' #62a420', '#89cbdf', '#004a70', '#9460b8'];
nameChecker = new Subject<string>();
labelForm: NgForm;
@ViewChild('labelForm')
currentForm: NgForm;
@ -66,16 +66,12 @@ export class CreateEditLabelComponent implements OnInit, OnDestroy {
) { }
ngOnInit(): void {
this.nameChecker.debounceTime(500).distinctUntilChanged().subscribe((name: string) => {
this.nameChecker.debounceTime(500).subscribe((name: string) => {
this.checkOnGoing = true;
toPromise<Label[]>(this.labelService.getLabels(this.scope, this.projectId))
toPromise<Label[]>(this.labelService.getLabels(this.scope, this.projectId, name))
.then(targets => {
if (targets && targets.length) {
if (targets.find(m => m.name === name)) {
this.isLabelNameExist = true;
} else {
this.isLabelNameExist = false;
};
this.isLabelNameExist = true;
}else {
this.isLabelNameExist = false;
}

View File

@ -140,6 +140,6 @@ export class RepositoryDefaultService extends RepositoryService {
return this.http.delete(url, HTTP_JSON_OPTIONS).toPromise()
.then(response => response)
.catch(error => { Promise.reject(error); });
.catch(error => {return Promise.reject(error); });
}
}

View File

@ -1,6 +1,5 @@
export const TAG_DETAIL_STYLES: string = `
.overview-section {
background-color: white;
padding-bottom: 36px;
border-bottom: 1px solid #cccccc;
}
@ -78,27 +77,37 @@ export const TAG_DETAIL_STYLES: string = `
padding-left: 24px;
}
.vulnerabilities-info .third-column {
.third-column {
margin-left: 36px;
}
.vulnerability{
margin-left: 50px;
margin-top: -12px;
margin-bottom: 20px;}
.vulnerabilities-info .second-column,
.vulnerabilities-info .fourth-column {
.vulnerabilities-info .second-column {
text-align: left;
margin-left: 6px;
}
.fourth-column{
float: left;
margin-left:20px;}
.vulnerabilities-info .second-row {
margin-top: 6px;
}
.detail-title {
font-weight: 500;
float:left;
font-weight: 600;
font-size: 14px;
}
.image-detail-label {
text-align: right;
margin-right: 10px;
text-align: left;
font-weight: 600;
}
.image-detail-value {

View File

@ -7,26 +7,22 @@ export const TAG_DETAIL_HTML: string = `
</div>
<div class="title-block">
<div class="tag-name">
<h1>{{tagDetails.name}}</h1>
</div>
<div class="tag-timestamp">
{{'TAG.CREATION_TIME_PREFIX' | translate }} {{tagDetails.created | date }} {{'TAG.CREATOR_PREFIX' | translate }} {{author | translate}}
<h1>{{repositoryId}}:{{tagDetails.name}}</h1>
</div>
</div>
</div>
<div class="summary-block">
<div class="image-summary">
<div class="detail-title">
{{'TAG.IMAGE_DETAILS' | translate }}
</div>
<div class="flex-block">
<div class="image-detail-label">
<div>{{'TAG.AUTHOR' | translate }}</div>
<div>{{'TAG.ARCHITECTURE' | translate }}</div>
<div>{{'TAG.OS' | translate }}</div>
<div>{{'TAG.DOCKER_VERSION' | translate }}</div>
<div>{{'TAG.SCAN_COMPLETION_TIME' | translate }}</div>
</div>
<div class="image-detail-value">
<div>{{author | translate}}</div>
<div>{{tagDetails.architecture}}</div>
<div>{{tagDetails.os}}</div>
<div>{{tagDetails.docker_version}}</div>
@ -35,8 +31,8 @@ export const TAG_DETAIL_HTML: string = `
</div>
</div>
<div>
<div class="detail-title">
{{'TAG.IMAGE_VULNERABILITIES' | translate }}
<div class="vulnerability">
<hbr-vulnerability-bar [repoName]="repositoryId" [tagId]="tagDetails.name" [summary]="tagDetails.scan_overview"></hbr-vulnerability-bar>
</div>
<div class="flex-block vulnerabilities-info">
<div>
@ -46,12 +42,6 @@ export const TAG_DETAIL_HTML: string = `
<div class="second-row">
<clr-icon shape="exclamation-triangle" size="24" class="tip-icon-medium"></clr-icon>
</div>
</div>
<div class="second-column">
<div>{{highCount}} {{'VULNERABILITY.SEVERITY.HIGH' | translate }}</div>
<div class="second-row">{{mediumCount}} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }}</div>
</div>
<div class="third-column">
<div>
<clr-icon shape="play" size="20" class="tip-icon-low rotate-90"></clr-icon>
</div>
@ -59,11 +49,20 @@ export const TAG_DETAIL_HTML: string = `
<clr-icon shape="help" size="18" style="margin-left: 2px;"></clr-icon>
</div>
</div>
<div class="fourth-column">
<div>{{lowCount}} {{'VULNERABILITY.SEVERITY.LOW' | translate }}</div>
<div class="second-row">{{unknownCount}} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }}</div>
<div class="second-column">
<div>{{highCount}} {{'VULNERABILITY.SEVERITY.HIGH' | translate }}{{'TAG.LEVEL_VULNERABILITIES' | translate }}</div>
<div class="second-row">{{mediumCount}} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }}{{'TAG.LEVEL_VULNERABILITIES' | translate }}</div>
<div>{{lowCount}} {{'VULNERABILITY.SEVERITY.LOW' | translate }}{{'TAG.LEVEL_VULNERABILITIES' | translate }}</div>
<div class="second-row">{{unknownCount}} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }}{{'TAG.LEVEL_VULNERABILITIES' | translate }}</div>
</div>
</div>
</div>
<div *ngIf="tagDetails.labels.length">
<div class="third-column detail-title">{{'TAG.LABELS' | translate }}</div>
<div class="fourth-column">
<div *ngFor="let label of tagDetails.labels" style="margin-bottom: 2px;"><hbr-label-piece [label]="label"></hbr-label-piece></div>
</div>
</div>
</div>
</section>

View File

@ -10,6 +10,11 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { TagService, TagDefaultService, ScanningResultService, ScanningResultDefaultService } from '../service/index';
import { FilterComponent } from '../filter/index';
import { VULNERABILITY_SCAN_STATUS } from '../utils';
import {VULNERABILITY_DIRECTIVES} from "../vulnerability-scanning/index";
import {LabelPieceComponent} from "../label-piece/label-piece.component";
import {JobLogViewerComponent} from "../job-log-viewer/job-log-viewer.component";
import {ChannelService} from "../channel/channel.service";
import {JobLogService, JobLogDefaultService} from "../service/job-log.service";
describe('TagDetailComponent (inline template)', () => {
@ -66,10 +71,16 @@ describe('TagDetailComponent (inline template)', () => {
declarations: [
TagDetailComponent,
ResultGridComponent,
VULNERABILITY_DIRECTIVES,
LabelPieceComponent,
JobLogViewerComponent,
FilterComponent
],
providers: [
ErrorHandler,
ChannelService,
JobLogDefaultService,
{provide: JobLogService, useClass: JobLogDefaultService},
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: TagService, useClass: TagDefaultService },
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }
@ -119,7 +130,7 @@ describe('TagDetailComponent (inline template)', () => {
let el: HTMLElement = fixture.nativeElement.querySelector('.tag-name');
expect(el).toBeTruthy();
expect(el.textContent.trim()).toEqual('nginx');
expect(el.textContent.trim()).toEqual('mock_repo:nginx');
});
}));
@ -133,7 +144,7 @@ describe('TagDetailComponent (inline template)', () => {
expect(el).toBeTruthy();
let el2: HTMLElement = el.querySelector('div');
expect(el2).toBeTruthy();
expect(el2.textContent).toEqual("amd64");
expect(el2.textContent).toEqual("steven");
});
}));
@ -147,7 +158,7 @@ describe('TagDetailComponent (inline template)', () => {
expect(el).toBeTruthy();
let el2: HTMLElement = el.querySelector('div');
expect(el2).toBeTruthy();
expect(el2.textContent.trim()).toEqual("13 VULNERABILITY.SEVERITY.HIGH");
expect(el2.textContent.trim()).toEqual("13 VULNERABILITY.SEVERITY.HIGHTAG.LEVEL_VULNERABILITIES");
});
}));

View File

@ -6,6 +6,7 @@ import { TAG_DETAIL_HTML } from './tag-detail.component.html';
import { TagService, Tag, VulnerabilitySeverity } from '../service/index';
import { toPromise } from '../utils';
import { ErrorHandler } from '../error-handler/index';
import {Label} from "../service/interface";
@Component({
selector: 'hbr-tag-detail',
@ -19,6 +20,7 @@ export class TagDetailComponent implements OnInit {
_mediumCount: number = 0;
_lowCount: number = 0;
_unknownCount: number = 0;
labels: Label;
@Input() tagId: string;
@Input() repositoryId: string;
@ -74,7 +76,7 @@ export class TagDetailComponent implements OnInit {
}
onBack(): void {
this.backEvt.emit(this.tagId);
this.backEvt.emit(this.repositoryId);
}
getPackageText(count: number): string {

View File

@ -66,4 +66,11 @@ export const TAG_STYLE = `
:host >>> .signpost-content-body{padding:0 .4rem;}
:host >>> .signpost-content-header{display:none;}
.filterLabelPiece{position: absolute; bottom :0px;z-index:1;}
.dropdown .dropdown-toggle.btn {
padding-right: 1rem;
border-left-width: 0;
border-right-width: 0;
border-radius: 0;
margin-top: -2px;
}
`;

View File

@ -21,7 +21,7 @@ export const TAG_TEMPLATE = `
<hbr-filter [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filter)="doSearchTagNames($event)" [currentValue]="lastFilteredTagName" clrDropdownTrigger></hbr-filter>
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
<div style='display:grid'>
<label class="dropdown-header">{{'REPOSITORY.ADD_TO_IMAGE' | translate}}</label>
<label class="dropdown-header">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label>
<div class="form-group"><input type="text" placeholder="Filter labels" #labelNamePiece (keyup)="handleInputFilter(labelNamePiece.value)"></div>
<div [hidden]='imageFilterLabels.length'>{{'LABEL.NO_LABELS' | translate }}</div>
<div [hidden]='!imageFilterLabels.length' style='max-height:300px;overflow-y: auto;'>
@ -52,10 +52,10 @@ export const TAG_TEMPLATE = `
<div class="form-group"><input type="text" placeholder="Filter labels" #stickLabelNamePiece (keyup)="handleStickInputFilter(stickLabelNamePiece.value)"></div>
<div [hidden]='imageStickLabels.length'>{{'LABEL.NO_LABELS' | translate }}</div>
<div [hidden]='!imageStickLabels.length' style='max-height:300px;overflow-y: auto;'>
<button type="button" class="dropdown-item" *ngFor='let label of imageStickLabels' (click)="label.iconsShow = true; selectLabel(label)">
<button type="button" class="dropdown-item" *ngFor='let label of imageStickLabels' (click)="selectLabel(label); label.iconsShow = true">
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'></clr-icon>
<div class='labelDiv'><hbr-label-piece [label]="label.label"></hbr-label-piece></div>
<clr-icon shape="times-circle" class='pull-right' [hidden]='!label.iconsShow' (click)="$event.stopPropagation(); label.iconsShow = false; unSelectLabel(label)"></clr-icon>
<clr-icon shape="times-circle" class='pull-right' [hidden]='!label.iconsShow' (click)="$event.stopPropagation(); unSelectLabel(label); label.iconsShow = false"></clr-icon>
</button>
</div>
</div>

View File

@ -140,13 +140,6 @@ describe('TagComponent (inline template)', () => {
labelService = fixture.debugElement.injector.get(LabelService);
/*spyLabels = spyOn(labelService, 'getLabels').and.callFake(function (param) {
if (param === 'g') {
return Promise.resolve(mockLabels);
}else {
Promise.resolve(mockLabels1)
}
})*/
spyLabels = spyOn(labelService, 'getGLabels').and.returnValues(Promise.resolve(mockLabels));
spyLabels1 = spyOn(labelService, 'getPLabels').and.returnValues(Promise.resolve(mockLabels1));

View File

@ -180,11 +180,11 @@ export class TagComponent implements OnInit, AfterViewInit {
.subscribe((name: string) => {
if (name && name.length) {
this.filterOnGoing = true;
this.imageFilterLabels = [];
this.imageStickLabels = [];
this.imageLabels.forEach(data => {
if (data.label.name.indexOf(name) !== -1) {
this.imageFilterLabels.push(data);
this.imageStickLabels.push(data);
}
})
setTimeout(() => {
@ -302,7 +302,7 @@ export class TagComponent implements OnInit, AfterViewInit {
this.selectedChange(tag);
}
selectLabel(labelInfo: {[key: string]: any | string[]}): void {
if (labelInfo && labelInfo.iconsShow) {
if (labelInfo && !labelInfo.iconsShow) {
let labelId = labelInfo.label.id;
this.selectedRow = this.selectedTag;
toPromise<any>(this.tagService.addLabelToImages(this.repoName, this.selectedRow[0].name, labelId)).then(res => {
@ -314,7 +314,7 @@ export class TagComponent implements OnInit, AfterViewInit {
}
unSelectLabel(labelInfo: {[key: string]: any | string[]}): void {
if (labelInfo && !labelInfo.iconsShow) {
if (labelInfo && labelInfo.iconsShow) {
let labelId = labelInfo.label.id;
this.selectedRow = this.selectedTag;
toPromise<any>(this.tagService.deleteLabelToImages(this.repoName, this.selectedRow[0].name, labelId)).then(res => {

View File

@ -31,7 +31,7 @@
"clarity-icons": "^0.10.17",
"clarity-ui": "^0.10.27",
"core-js": "^2.4.1",
"harbor-ui": "0.6.53",
"harbor-ui": "0.6.57",
"intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0",

View File

@ -108,6 +108,14 @@ const harborRoutes: Routes = [
projectResolver: ProjectRoutingResolver
}
},
{
path: 'projects/:id/repositories/:repo/tags/:tag',
component: TagDetailPageComponent,
canActivate: [MemberGuard],
resolve: {
projectResolver: ProjectRoutingResolver
},
},
{
path: 'projects/:id',
component: ProjectDetailComponent,
@ -124,10 +132,6 @@ const harborRoutes: Routes = [
path: 'repositories/:repo/tags',
component: TagRepositoryComponent,
},
{
path: 'repositories/:repo/tags/:tag',
component: TagDetailPageComponent
},
{
path: 'replications',
component: ReplicationPageComponent,

View File

@ -1,3 +1,3 @@
<div style="margin-top: 24px;">
<div>
<hbr-tag-detail (backEvt)="goBack($event)" [tagId]="tagId" [repositoryId]="repositoryId"></hbr-tag-detail>
</div>

View File

@ -32,10 +32,10 @@ export class TagDetailPageComponent implements OnInit {
ngOnInit(): void {
this.repositoryId = this.route.snapshot.params["repo"];
this.tagId = this.route.snapshot.params["tag"];
this.projectId = this.route.snapshot.parent.params["id"];
this.projectId = this.route.snapshot.params["id"];
}
goBack(tag: string): void {
this.router.navigate(["harbor", "projects", this.projectId, "repositories"]);
this.router.navigate(["harbor", "projects", this.projectId, "repositories", tag]);
}
}

View File

@ -415,6 +415,7 @@
"IMAGE": "Images",
"LABELS": ":labels",
"ADD_TO_IMAGE": "Add labels to this image",
"FILTER_BY_LABEL": "Filter projects by label",
"ADD_LABELS": "Add labels"
},
"ALERT": {
@ -438,6 +439,8 @@
"REPLICATION": "Replication",
"EMAIL": "Email",
"LABEL": "Label",
"REPOSITORY": "Repository",
"REPO_READ_ONLY": "Repository Read Only",
"SYSTEM": "System Settings",
"VULNERABILITY": "Vulnerability",
"CONFIRM_TITLE": "Confirm to cancel",

View File

@ -413,7 +413,7 @@
"INFO": "Información",
"NO_INFO": "Sin información de descripción para este repositorio",
"IMAGE": "Imágenes",
"LABELS": ":labels",
"LABELS": "Labels",
"ADD_TO_IMAGE": "Add labels to this image",
"ADD_LABELS": "Add labels"
},
@ -438,6 +438,8 @@
"REPLICATION": "Replicación",
"EMAIL": "Email",
"LABEL": "Label",
"REPOSITORY": "Repository",
"REPO_READ_ONLY": "Repository Read Only",
"SYSTEM": "Opciones del Sistema",
"VULNERABILITY": "Vulnerability",
"CONFIRM_TITLE": "Confirma cancelación",
@ -613,9 +615,12 @@
"OS": "OS",
"SCAN_COMPLETION_TIME": "Scan Completed",
"IMAGE_VULNERABILITIES": "Image Vulnerabilities",
"LEVEL_VULNERABILITIES": "Level Vulnerabilities",
"PLACEHOLDER": "We couldn't find any tags!",
"COPY_ERROR": "Copy failed, please try to manually copy.",
"FILTER_FOR_TAGS": "Etiquetas de filtro"
"FILTER_FOR_TAGS": "Etiquetas de filtro",
"AUTHOR": "Author",
"LABELS": "LABELS"
},
"LABEL": {
"LABEL": "Label",

View File

@ -438,6 +438,8 @@
"REPLICATION": "复制",
"EMAIL": "邮箱",
"LABEL": "标签",
"REPOSITORY": "仓库",
"REPO_READ_ONLY": "仓库只读",
"SYSTEM": "系统设置",
"VULNERABILITY": "漏洞",
"CONFIRM_TITLE": "确认取消",
@ -613,9 +615,12 @@
"OS": "操作系统",
"SCAN_COMPLETION_TIME": "扫描完成时间",
"IMAGE_VULNERABILITIES": "镜像缺陷",
"LEVEL_VULNERABILITIES": "缺陷等级",
"PLACEHOLDER": "未发现任何标签!",
"COPY_ERROR": "拷贝失败,请尝试手动拷贝。",
"FILTER_FOR_TAGS": "过滤项目"
"FILTER_FOR_TAGS": "过滤项目",
"AUTHOR": "作者",
"LABELS": "标签"
},
"LABEL": {
"LABEL": "标签",