mirror of
https://github.com/goharbor/harbor.git
synced 2024-07-01 17:25:06 +02:00
Update components for pagination and sorting.
This commit is contained in:
parent
565110d9f1
commit
e1df278ab5
|
@ -203,6 +203,7 @@ export class CreateEditEndpointComponent implements AfterViewChecked {
|
||||||
let payload: Endpoint = this.initEndpoint;
|
let payload: Endpoint = this.initEndpoint;
|
||||||
if(this.targetNameHasChanged) {
|
if(this.targetNameHasChanged) {
|
||||||
payload.name = this.target.name;
|
payload.name = this.target.name;
|
||||||
|
delete payload.endpoint;
|
||||||
}
|
}
|
||||||
if (this.endpointHasChanged) {
|
if (this.endpointHasChanged) {
|
||||||
payload.endpoint = this.target.endpoint;
|
payload.endpoint = this.target.endpoint;
|
||||||
|
|
|
@ -16,10 +16,10 @@ export const ENDPOINT_TEMPLATE: string = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<clr-datagrid>
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-column>{{'DESTINATION.NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'name'">{{'DESTINATION.NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'DESTINATION.URL' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'endpoint'">{{'DESTINATION.URL' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'>
|
<clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'>
|
||||||
<clr-dg-action-overflow>
|
<clr-dg-action-overflow>
|
||||||
<button class="action-item" (click)="editTarget(t)">{{'DESTINATION.TITLE_EDIT' | translate}}</button>
|
<button class="action-item" (click)="editTarget(t)">{{'DESTINATION.TITLE_EDIT' | translate}}</button>
|
||||||
|
@ -29,7 +29,11 @@ export const ENDPOINT_TEMPLATE: string = `
|
||||||
<clr-dg-cell>{{t.endpoint}}</clr-dg-cell>
|
<clr-dg-cell>{{t.endpoint}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.creation_time | date: 'short'}}</clr-dg-cell>
|
<clr-dg-cell>{{t.creation_time | date: 'short'}}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>{{ (targets ? targets.length : 0) }} {{'DESTINATION.ITEMS' | translate}}</clr-dg-footer>
|
<clr-dg-footer>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}
|
||||||
|
{{pagination.totalItems}} {{'DESTINATION.ITEMS' | translate}}
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -116,8 +116,9 @@ describe('EndpointComponent (inline template)', () => {
|
||||||
|
|
||||||
it('should open create endpoint modal', async(() => {
|
it('should open create endpoint modal', async(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
comp.editTarget(mockOne);
|
fixture.whenStable().then(()=>{
|
||||||
fixture.whenStable().then(()=>{
|
fixture.detectChanges();
|
||||||
|
comp.editTarget(mockOne);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(comp.target.name).toEqual('target_01');
|
expect(comp.target.name).toEqual('target_01');
|
||||||
});
|
});
|
||||||
|
@ -125,7 +126,6 @@ describe('EndpointComponent (inline template)', () => {
|
||||||
|
|
||||||
it('should filter endpoints by keyword', async(() => {
|
it('should filter endpoints by keyword', async(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
fixture.whenStable().then(()=>{
|
fixture.whenStable().then(()=>{
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
comp.doSearchTargets('target_02');
|
comp.doSearchTargets('target_02');
|
||||||
|
|
|
@ -32,7 +32,9 @@ import { CreateEditEndpointComponent } from '../create-edit-endpoint/create-edit
|
||||||
import { ENDPOINT_STYLE } from './endpoint.component.css';
|
import { ENDPOINT_STYLE } from './endpoint.component.css';
|
||||||
import { ENDPOINT_TEMPLATE } from './endpoint.component.html';
|
import { ENDPOINT_TEMPLATE } from './endpoint.component.html';
|
||||||
|
|
||||||
import { toPromise } from '../utils';
|
import { toPromise, CustomComparator } from '../utils';
|
||||||
|
|
||||||
|
import { State, Comparator } from 'clarity-angular';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hbr-endpoint',
|
selector: 'hbr-endpoint',
|
||||||
|
@ -55,6 +57,10 @@ export class EndpointComponent implements OnInit {
|
||||||
targetName: string;
|
targetName: string;
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
|
|
||||||
|
loading: boolean = false;
|
||||||
|
|
||||||
|
creationTimeComparator: Comparator<Endpoint> = new CustomComparator<Endpoint>('creation_time', 'date');
|
||||||
|
|
||||||
get initEndpoint(): Endpoint {
|
get initEndpoint(): Endpoint {
|
||||||
return {
|
return {
|
||||||
endpoint: "",
|
endpoint: "",
|
||||||
|
@ -101,7 +107,7 @@ export class EndpointComponent implements OnInit {
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.targetName = '';
|
this.targetName = '';
|
||||||
this.retrieve('');
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
@ -110,29 +116,34 @@ export class EndpointComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieve(targetName: string): void {
|
retrieve(): void {
|
||||||
|
this.loading = true;
|
||||||
toPromise<Endpoint[]>(this.endpointService
|
toPromise<Endpoint[]>(this.endpointService
|
||||||
.getEndpoints(targetName))
|
.getEndpoints(this.targetName))
|
||||||
.then(
|
.then(
|
||||||
targets => {
|
targets => {
|
||||||
this.targets = targets || [];
|
this.targets = targets || [];
|
||||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
||||||
setTimeout(()=>clearInterval(hnd), 1000);
|
setTimeout(()=>clearInterval(hnd), 1000);
|
||||||
}).catch(error => this.errorHandler.error(error));
|
this.loading = false;
|
||||||
|
}).catch(error => {
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doSearchTargets(targetName: string) {
|
doSearchTargets(targetName: string) {
|
||||||
this.targetName = targetName;
|
this.targetName = targetName;
|
||||||
this.retrieve(targetName);
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshTargets() {
|
refreshTargets() {
|
||||||
this.retrieve('');
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
reload($event: any) {
|
reload($event: any) {
|
||||||
this.targetName = '';
|
this.targetName = '';
|
||||||
this.retrieve('');
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
openModal() {
|
openModal() {
|
||||||
|
|
|
@ -290,6 +290,7 @@ export const EN_US_LANG: any = {
|
||||||
"INVALID_NAME": "Invalid endpoint name.",
|
"INVALID_NAME": "Invalid endpoint name.",
|
||||||
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
||||||
"CREATION_TIME": "Creation Time",
|
"CREATION_TIME": "Creation Time",
|
||||||
|
"OF": "of",
|
||||||
"ITEMS": "item(s)",
|
"ITEMS": "item(s)",
|
||||||
"CREATED_SUCCESS": "Created endpoint successfully.",
|
"CREATED_SUCCESS": "Created endpoint successfully.",
|
||||||
"UPDATED_SUCCESS": "Updated endpoint successfully.",
|
"UPDATED_SUCCESS": "Updated endpoint successfully.",
|
||||||
|
@ -299,8 +300,7 @@ export const EN_US_LANG: any = {
|
||||||
"FAILED_TO_DELETE_TARGET_IN_USED": "Failed to delete the endpoint in use."
|
"FAILED_TO_DELETE_TARGET_IN_USED": "Failed to delete the endpoint in use."
|
||||||
},
|
},
|
||||||
"REPOSITORY": {
|
"REPOSITORY": {
|
||||||
"COPY_ID": "Copy ID",
|
"COPY_DIGEST_ID": "Copy Digest ID",
|
||||||
"COPY_PARENT_ID": "Copy Parent ID",
|
|
||||||
"DELETE": "Delete",
|
"DELETE": "Delete",
|
||||||
"NAME": "Name",
|
"NAME": "Name",
|
||||||
"TAGS_COUNT": "Tags",
|
"TAGS_COUNT": "Tags",
|
||||||
|
@ -324,6 +324,7 @@ export const EN_US_LANG: any = {
|
||||||
"OS": "OS",
|
"OS": "OS",
|
||||||
"SHOW_DETAILS": "Show Details",
|
"SHOW_DETAILS": "Show Details",
|
||||||
"REPOSITORIES": "Repositories",
|
"REPOSITORIES": "Repositories",
|
||||||
|
"OF": "of",
|
||||||
"ITEMS": "item(s)",
|
"ITEMS": "item(s)",
|
||||||
"POP_REPOS": "Popular Repositories",
|
"POP_REPOS": "Popular Repositories",
|
||||||
"DELETED_REPO_SUCCESS": "Deleted repository successfully.",
|
"DELETED_REPO_SUCCESS": "Deleted repository successfully.",
|
||||||
|
|
|
@ -290,6 +290,7 @@ export const ZH_CN_LANG: any = {
|
||||||
"INVALID_NAME": "无效的目标名称。",
|
"INVALID_NAME": "无效的目标名称。",
|
||||||
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
||||||
"CREATION_TIME": "创建时间",
|
"CREATION_TIME": "创建时间",
|
||||||
|
"OF": "共计",
|
||||||
"ITEMS": "条记录",
|
"ITEMS": "条记录",
|
||||||
"CREATED_SUCCESS": "成功创建目标。",
|
"CREATED_SUCCESS": "成功创建目标。",
|
||||||
"UPDATED_SUCCESS": "成功更新目标。",
|
"UPDATED_SUCCESS": "成功更新目标。",
|
||||||
|
@ -299,8 +300,7 @@ export const ZH_CN_LANG: any = {
|
||||||
"FAILED_TO_DELETE_TARGET_IN_USED": "无法删除正在使用的目标。"
|
"FAILED_TO_DELETE_TARGET_IN_USED": "无法删除正在使用的目标。"
|
||||||
},
|
},
|
||||||
"REPOSITORY": {
|
"REPOSITORY": {
|
||||||
"COPY_ID": "复制ID",
|
"COPY_DIGEST_ID": "复制摘要ID",
|
||||||
"COPY_PARENT_ID": "复制父级ID",
|
|
||||||
"DELETE": "删除",
|
"DELETE": "删除",
|
||||||
"NAME": "名称",
|
"NAME": "名称",
|
||||||
"TAGS_COUNT": "标签数",
|
"TAGS_COUNT": "标签数",
|
||||||
|
@ -324,6 +324,7 @@ export const ZH_CN_LANG: any = {
|
||||||
"OS": "操作系统",
|
"OS": "操作系统",
|
||||||
"SHOW_DETAILS": "显示详细",
|
"SHOW_DETAILS": "显示详细",
|
||||||
"REPOSITORIES": "镜像仓库",
|
"REPOSITORIES": "镜像仓库",
|
||||||
|
"OF": "共计",
|
||||||
"ITEMS": "条记录",
|
"ITEMS": "条记录",
|
||||||
"POP_REPOS": "受欢迎的镜像仓库",
|
"POP_REPOS": "受欢迎的镜像仓库",
|
||||||
"DELETED_REPO_SUCCESS": "成功删除镜像仓库。",
|
"DELETED_REPO_SUCCESS": "成功删除镜像仓库。",
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
||||||
<confirmation-dialog #toggleConfirmDialog (confirmAction)="toggleConfirm($event)"></confirmation-dialog>
|
<confirmation-dialog #toggleConfirmDialog (confirmAction)="toggleConfirm($event)"></confirmation-dialog>
|
||||||
<confirmation-dialog #deletionConfirmDialog (confirmAction)="deletionConfirm($event)"></confirmation-dialog>
|
<confirmation-dialog #deletionConfirmDialog (confirmAction)="deletionConfirm($event)"></confirmation-dialog>
|
||||||
<clr-datagrid>
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column *ngIf="projectless">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'project_name'" *ngIf="projectless">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'description'">{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'target_name'">{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.LAST_START_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'REPLICATION.LAST_START_TIME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.ACTIVATION' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="enabledComparator">{{'REPLICATION.ACTIVATION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *clrDgItems="let p of rules" [clrDgItem]="p" (click)="selectRule(p)" [style.backgroundColor]="(!projectless && selectedId === p.id) ? '#eee' : ''">
|
<clr-dg-row *clrDgItems="let p of rules" [clrDgItem]="p" (click)="selectRule(p)" [style.backgroundColor]="(!projectless && selectedId === p.id) ? '#eee' : ''">
|
||||||
<clr-dg-action-overflow>
|
<clr-dg-action-overflow>
|
||||||
<button class="action-item" (click)="editRule(p)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
<button class="action-item" (click)="editRule(p)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
||||||
|
@ -27,7 +27,7 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
||||||
<clr-dg-cell>{{p.target_name}}</clr-dg-cell>
|
<clr-dg-cell>{{p.target_name}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<ng-template [ngIf]="p.start_time === nullTime">-</ng-template>
|
<ng-template [ngIf]="p.start_time === nullTime">-</ng-template>
|
||||||
<ng-template [ngIf]="p.start_time !== nullTime">{{p.start_time}}</ng-template>
|
<ng-template [ngIf]="p.start_time !== nullTime">{{p.start_time | date: 'short'}}</ng-template>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
{{ (p.enabled === 1 ? 'REPLICATION.ENABLED' : 'REPLICATION.DISABLED') | translate}}
|
{{ (p.enabled === 1 ? 'REPLICATION.ENABLED' : 'REPLICATION.DISABLED') | translate}}
|
||||||
|
|
|
@ -25,9 +25,9 @@ import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { ErrorHandler } from '../error-handler/error-handler';
|
import { ErrorHandler } from '../error-handler/error-handler';
|
||||||
import { toPromise } from '../utils';
|
import { toPromise, CustomComparator } from '../utils';
|
||||||
|
|
||||||
import { State } from 'clarity-angular';
|
import { State, Comparator } from 'clarity-angular';
|
||||||
|
|
||||||
import { LIST_REPLICATION_RULE_TEMPLATE } from './list-replication-rule.component.html';
|
import { LIST_REPLICATION_RULE_TEMPLATE } from './list-replication-rule.component.html';
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ export class ListReplicationRuleComponent {
|
||||||
@Input() projectless: boolean;
|
@Input() projectless: boolean;
|
||||||
@Input() selectedId: number | string;
|
@Input() selectedId: number | string;
|
||||||
|
|
||||||
|
@Input() loading: boolean = false;
|
||||||
|
|
||||||
@Output() reload = new EventEmitter<boolean>();
|
@Output() reload = new EventEmitter<boolean>();
|
||||||
@Output() selectOne = new EventEmitter<ReplicationRule>();
|
@Output() selectOne = new EventEmitter<ReplicationRule>();
|
||||||
@Output() editOne = new EventEmitter<ReplicationRule>();
|
@Output() editOne = new EventEmitter<ReplicationRule>();
|
||||||
|
@ -55,11 +57,14 @@ export class ListReplicationRuleComponent {
|
||||||
@ViewChild('deletionConfirmDialog')
|
@ViewChild('deletionConfirmDialog')
|
||||||
deletionConfirmDialog: ConfirmationDialogComponent;
|
deletionConfirmDialog: ConfirmationDialogComponent;
|
||||||
|
|
||||||
|
startTimeComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>('start_time', 'date');
|
||||||
|
enabledComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>('enabled', 'number');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private replicationService: ReplicationService,
|
private replicationService: ReplicationService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private errorHandler: ErrorHandler,
|
private errorHandler: ErrorHandler,
|
||||||
private ref: ChangeDetectorRef) {
|
private ref: ChangeDetectorRef) {
|
||||||
setInterval(()=>ref.markForCheck(), 500);
|
setInterval(()=>ref.markForCheck(), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
export const LIST_REPOSITORY_TEMPLATE = `
|
export const LIST_REPOSITORY_TEMPLATE = `
|
||||||
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
||||||
<clr-dg-column>{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *clrDgItems="let r of repositories" [clrDgItem]='r'>
|
<clr-dg-row *clrDgItems="let r of repositories" [clrDgItem]='r'>
|
||||||
<clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
|
<clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
|
||||||
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||||
|
@ -12,7 +12,8 @@ export const LIST_REPOSITORY_TEMPLATE = `
|
||||||
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
{{(repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}}
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
|
||||||
<clr-dg-pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
{{pagination.totalItems}}{{'REPOSITORY.ITEMS' | translate}}
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>`;
|
</clr-datagrid>`;
|
|
@ -1,10 +1,12 @@
|
||||||
import { Component, Input, Output, EventEmitter, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
|
||||||
import { State } from 'clarity-angular';
|
import { State, Comparator } from 'clarity-angular';
|
||||||
|
|
||||||
import { Repository } from '../service/interface';
|
import { Repository } from '../service/interface';
|
||||||
import { LIST_REPOSITORY_TEMPLATE } from './list-repository.component.html';
|
import { LIST_REPOSITORY_TEMPLATE } from './list-repository.component.html';
|
||||||
|
|
||||||
|
import { CustomComparator } from '../utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hbr-list-repository',
|
selector: 'hbr-list-repository',
|
||||||
template: LIST_REPOSITORY_TEMPLATE,
|
template: LIST_REPOSITORY_TEMPLATE,
|
||||||
|
@ -21,6 +23,10 @@ export class ListRepositoryComponent {
|
||||||
|
|
||||||
pageOffset: number = 1;
|
pageOffset: number = 1;
|
||||||
|
|
||||||
|
pullCountComparator: Comparator<Repository> = new CustomComparator<Repository>('pull_count', 'number');
|
||||||
|
|
||||||
|
tagsCountComparator: Comparator<Repository> = new CustomComparator<Repository>('tags_count', 'number');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private ref: ChangeDetectorRef) {
|
private ref: ChangeDetectorRef) {
|
||||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
||||||
|
|
|
@ -19,9 +19,11 @@ import {
|
||||||
} from '../service/index';
|
} from '../service/index';
|
||||||
import { ErrorHandler } from '../error-handler/index';
|
import { ErrorHandler } from '../error-handler/index';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { toPromise } from '../utils';
|
import { toPromise, CustomComparator } from '../utils';
|
||||||
import { LOG_TEMPLATE, LOG_STYLES } from './recent-log.template';
|
import { LOG_TEMPLATE, LOG_STYLES } from './recent-log.template';
|
||||||
|
|
||||||
|
import { Comparator } from 'clarity-angular';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hbr-log',
|
selector: 'hbr-log',
|
||||||
styles: [LOG_STYLES],
|
styles: [LOG_STYLES],
|
||||||
|
@ -35,6 +37,10 @@ export class RecentLogComponent implements OnInit {
|
||||||
lines: number = 10; //Support 10, 25 and 50
|
lines: number = 10; //Support 10, 25 and 50
|
||||||
currentTerm: string;
|
currentTerm: string;
|
||||||
|
|
||||||
|
loading: boolean;
|
||||||
|
|
||||||
|
opTimeComparator: Comparator<AccessLog> = new CustomComparator<AccessLog>('op_time', 'date');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private logService: AccessLogService,
|
private logService: AccessLogService,
|
||||||
private errorHandler: ErrorHandler) { }
|
private errorHandler: ErrorHandler) { }
|
||||||
|
@ -81,14 +87,17 @@ export class RecentLogComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onGoing = true;
|
this.onGoing = true;
|
||||||
|
this.loading = true;
|
||||||
toPromise<AccessLog[]>(this.logService.getRecentLogs(this.lines))
|
toPromise<AccessLog[]>(this.logService.getRecentLogs(this.lines))
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.onGoing = false;
|
this.onGoing = false;
|
||||||
|
this.loading = false;
|
||||||
this.logsCache = response; //Keep the data
|
this.logsCache = response; //Keep the data
|
||||||
this.recentLogs = this.logsCache.filter(log => log.username != "");//To display
|
this.recentLogs = this.logsCache.filter(log => log.username != "");//To display
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.onGoing = false;
|
this.onGoing = false;
|
||||||
|
this.loading = false;
|
||||||
this.errorHandler.error(error);
|
this.errorHandler.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,18 +24,18 @@ export const LOG_TEMPLATE: string = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<clr-datagrid>
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'username'">{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.REPOSITORY_NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'repo_name'">{{'AUDIT_LOG.REPOSITORY_NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.TAGS' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'repo_tag'">{{'AUDIT_LOG.TAGS' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.OPERATION' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'operation'">{{'AUDIT_LOG.OPERATION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.TIMESTAMP' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="opTimeComparator">{{'AUDIT_LOG.TIMESTAMP' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *clrDgItems="let l of recentLogs">
|
<clr-dg-row *clrDgItems="let l of recentLogs">
|
||||||
<clr-dg-cell>{{l.username}}</clr-dg-cell>
|
<clr-dg-cell>{{l.username}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.repo_name}}</clr-dg-cell>
|
<clr-dg-cell>{{l.repo_name}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.repo_tag}}</clr-dg-cell>
|
<clr-dg-cell>{{l.repo_tag}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.operation}}</clr-dg-cell>
|
<clr-dg-cell>{{l.operation}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.op_time}}</clr-dg-cell>
|
<clr-dg-cell>{{l.op_time | date: 'short'}}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>{{ (recentLogs ? recentLogs.length : 0) }} {{'AUDIT_LOG.ITEMS' | translate}}</clr-dg-footer>
|
<clr-dg-footer>{{ (recentLogs ? recentLogs.length : 0) }} {{'AUDIT_LOG.ITEMS' | translate}}</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const REPLICATION_TEMPLATE: string = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<list-replication-rule [rules]="changedRules" [projectless]="false" [selectedId]="initSelectedId" (selectOne)="selectOneRule($event)" (editOne)="openEditRule($event)" (reload)="reloadRules($event)"></list-replication-rule>
|
<list-replication-rule [rules]="changedRules" [projectless]="false" [selectedId]="initSelectedId" (selectOne)="selectOneRule($event)" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading"></list-replication-rule>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
|
@ -46,19 +46,19 @@ export const REPLICATION_TEMPLATE: string = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<clr-datagrid>
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'repository'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'operation'">{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.END_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="updateTimeComparator">{{'REPLICATION.END_TIME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *clrDgItems="let j of jobs" [clrDgItem]='j'>
|
<clr-dg-row *clrDgItems="let j of jobs" [clrDgItem]='j'>
|
||||||
<clr-dg-cell>{{j.repository}}</clr-dg-cell>
|
<clr-dg-cell>{{j.repository}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{j.status}}</clr-dg-cell>
|
<clr-dg-cell>{{j.status}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{j.operation}}</clr-dg-cell>
|
<clr-dg-cell>{{j.operation}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{j.creation_time}}</clr-dg-cell>
|
<clr-dg-cell>{{j.creation_time | date: 'short'}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{j.update_time}}</clr-dg-cell>
|
<clr-dg-cell>{{j.update_time | date: 'short'}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<a href="/api/jobs/replication/{{j.id}}/log" target="_BLANK">
|
<a href="/api/jobs/replication/{{j.id}}/log" target="_BLANK">
|
||||||
<clr-icon shape="clipboard"></clr-icon>
|
<clr-icon shape="clipboard"></clr-icon>
|
||||||
|
|
|
@ -24,7 +24,9 @@ import { ReplicationService } from '../service/replication.service';
|
||||||
import { RequestQueryParams } from '../service/RequestQueryParams';
|
import { RequestQueryParams } from '../service/RequestQueryParams';
|
||||||
import { ReplicationRule, ReplicationJob, Endpoint } from '../service/interface';
|
import { ReplicationRule, ReplicationJob, Endpoint } from '../service/interface';
|
||||||
|
|
||||||
import { toPromise } from '../utils';
|
import { toPromise, CustomComparator } from '../utils';
|
||||||
|
|
||||||
|
import { Comparator } from 'clarity-angular';
|
||||||
|
|
||||||
import { REPLICATION_TEMPLATE } from './replication.component.html';
|
import { REPLICATION_TEMPLATE } from './replication.component.html';
|
||||||
import { REPLICATION_STYLE } from './replication.component.css';
|
import { REPLICATION_STYLE } from './replication.component.css';
|
||||||
|
@ -81,8 +83,10 @@ export class ReplicationComponent implements OnInit {
|
||||||
changedRules: ReplicationRule[];
|
changedRules: ReplicationRule[];
|
||||||
initSelectedId: number | string;
|
initSelectedId: number | string;
|
||||||
|
|
||||||
rules: ReplicationRule[];
|
rules: ReplicationRule[];
|
||||||
jobs: ReplicationJob[];
|
loading: boolean;
|
||||||
|
|
||||||
|
jobs: ReplicationJob[];
|
||||||
|
|
||||||
jobsTotalRecordCount: number;
|
jobsTotalRecordCount: number;
|
||||||
jobsTotalPage: number;
|
jobsTotalPage: number;
|
||||||
|
@ -93,23 +97,28 @@ export class ReplicationComponent implements OnInit {
|
||||||
@ViewChild(CreateEditRuleComponent)
|
@ViewChild(CreateEditRuleComponent)
|
||||||
createEditPolicyComponent: CreateEditRuleComponent;
|
createEditPolicyComponent: CreateEditRuleComponent;
|
||||||
|
|
||||||
|
creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date');
|
||||||
|
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('update_time', 'date');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private errorHandler: ErrorHandler,
|
private errorHandler: ErrorHandler,
|
||||||
private replicationService: ReplicationService,
|
private replicationService: ReplicationService,
|
||||||
private translateService: TranslateService) {
|
private translateService: TranslateService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit() {
|
||||||
if(!this.projectId) {
|
if(!this.projectId) {
|
||||||
this.errorHandler.warning('Project ID is unset.');
|
this.errorHandler.warning('Project ID is unset.');
|
||||||
}
|
}
|
||||||
this.currentRuleStatus = this.ruleStatus[0];
|
this.currentRuleStatus = this.ruleStatus[0];
|
||||||
this.currentJobStatus = this.jobStatus[0];
|
this.currentJobStatus = this.jobStatus[0];
|
||||||
this.currentJobSearchOption = 0;
|
this.currentJobSearchOption = 0;
|
||||||
|
|
||||||
this.retrieveRules();
|
this.retrieveRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveRules(): void {
|
retrieveRules(): void {
|
||||||
|
this.loading = true;
|
||||||
toPromise<ReplicationRule[]>(this.replicationService
|
toPromise<ReplicationRule[]>(this.replicationService
|
||||||
.getReplicationRules(this.projectId, this.search.ruleName))
|
.getReplicationRules(this.projectId, this.search.ruleName))
|
||||||
.then(response=>{
|
.then(response=>{
|
||||||
|
@ -122,8 +131,12 @@ export class ReplicationComponent implements OnInit {
|
||||||
this.search.ruleId = this.changedRules[0].id || '';
|
this.search.ruleId = this.changedRules[0].id || '';
|
||||||
this.fetchReplicationJobs();
|
this.fetchReplicationJobs();
|
||||||
}
|
}
|
||||||
|
this.loading = false;
|
||||||
}
|
}
|
||||||
).catch(error=>this.errorHandler.error(error));
|
).catch(error=>{
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openModal(): void {
|
openModal(): void {
|
||||||
|
@ -147,13 +160,15 @@ export class ReplicationComponent implements OnInit {
|
||||||
params.set('repository', this.search.repoName);
|
params.set('repository', this.search.repoName);
|
||||||
params.set('start_time', this.search.startTimestamp);
|
params.set('start_time', this.search.startTimestamp);
|
||||||
params.set('end_time', this.search.endTimestamp);
|
params.set('end_time', this.search.endTimestamp);
|
||||||
|
|
||||||
toPromise<ReplicationJob[]>(this.replicationService
|
toPromise<ReplicationJob[]>(this.replicationService
|
||||||
.getJobs(this.search.ruleId, params))
|
.getJobs(this.search.ruleId, params))
|
||||||
.then(
|
.then(
|
||||||
response=>{
|
response=>{
|
||||||
this.jobs = response;
|
this.jobs = response;
|
||||||
}).catch(error=>this.errorHandler.error(error));
|
}).catch(error=>{
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOneRule(rule: ReplicationRule) {
|
selectOneRule(rule: ReplicationRule) {
|
||||||
|
|
|
@ -11,30 +11,6 @@ export interface Base {
|
||||||
update_time?: Date;
|
update_time?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for tag history
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface TagCompatibility
|
|
||||||
*/
|
|
||||||
export interface TagCompatibility {
|
|
||||||
v1Compatibility: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for tag manifest
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface TagManifest
|
|
||||||
*/
|
|
||||||
export interface TagManifest {
|
|
||||||
schemaVersion: number;
|
|
||||||
name: string;
|
|
||||||
tag: string;
|
|
||||||
architecture: string;
|
|
||||||
history: TagCompatibility[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for Repository
|
* Interface for Repository
|
||||||
*
|
*
|
||||||
|
@ -59,10 +35,16 @@ export interface Repository extends Base {
|
||||||
* @interface Tag
|
* @interface Tag
|
||||||
* @extends {Base}
|
* @extends {Base}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface Tag extends Base {
|
export interface Tag extends Base {
|
||||||
tag: string;
|
digest: string;
|
||||||
manifest: TagManifest;
|
name: string;
|
||||||
signed?: number; //May NOT exist
|
architecture: string;
|
||||||
|
os: string;
|
||||||
|
docker_version: string;
|
||||||
|
author: string;
|
||||||
|
created: Date;
|
||||||
|
signature?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -87,7 +87,7 @@ export class RepositoryDefaultService extends RepositoryService {
|
||||||
return Promise.reject('Bad argument');
|
return Promise.reject('Bad argument');
|
||||||
}
|
}
|
||||||
let url: string = this.config.repositoryBaseEndpoint ? this.config.repositoryBaseEndpoint : '/api/repositories';
|
let url: string = this.config.repositoryBaseEndpoint ? this.config.repositoryBaseEndpoint : '/api/repositories';
|
||||||
url = `${url}/${repositoryName}/tags`;
|
url = `${url}/${repositoryName}`;
|
||||||
|
|
||||||
return this.http.delete(url, HTTP_JSON_OPTIONS).toPromise()
|
return this.http.delete(url, HTTP_JSON_OPTIONS).toPromise()
|
||||||
.then(response => response)
|
.then(response => response)
|
||||||
|
|
|
@ -4,41 +4,24 @@ import { TagService, TagDefaultService } from './tag.service';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
|
|
||||||
import { Tag, TagCompatibility, TagManifest } from './interface';
|
import { Tag } from './interface';
|
||||||
|
|
||||||
import { VerifiedSignature } from './tag.service';
|
import { VerifiedSignature } from './tag.service';
|
||||||
import { toPromise } from '../utils';
|
import { toPromise } from '../utils';
|
||||||
|
|
||||||
describe('TagService', () => {
|
describe('TagService', () => {
|
||||||
let mockComp: TagCompatibility[] = [{
|
let mockTags: Tag[] = [
|
||||||
v1Compatibility: '{"architecture":"amd64","author":"NGINX Docker Maintainers \\"docker-maint@nginx.com\\"","config":{"Hostname":"6b3797ab1e90","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"443/tcp":{},"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.5-1~jessie"],"Cmd":["nginx","-g","daemon off;"],"ArgsEscaped":true,"Image":"sha256:47a33f0928217b307cf9f20920a0c6445b34ae974a60c1b4fe73b809379ad928","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":[],"Labels":{}},"container":"f1883a3fb44b0756a2a3b1e990736a44b1387183125351370042ce7bd9ffc338","container_config":{"Hostname":"6b3797ab1e90","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"443/tcp":{},"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.5-1~jessie"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\\"nginx\\" \\"-g\\" \\"daemon off;\\"]"],"ArgsEscaped":true,"Image":"sha256:47a33f0928217b307cf9f20920a0c6445b34ae974a60c1b4fe73b809379ad928","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":[],"Labels":{}},"created":"2016-11-08T22:41:15.912313785Z","docker_version":"1.12.3","id":"db3700426e6d7c1402667f42917109b2467dd49daa85d38ac99854449edc20b3","os":"linux","parent":"f3ef5f96caf99a18c6821487102c136b00e0275b1da0c7558d7090351f9d447e","throwaway":true}'
|
{
|
||||||
}];
|
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||||
let mockManifest: TagManifest = {
|
"name": "1.11.5",
|
||||||
schemaVersion: 1,
|
"architecture": "amd64",
|
||||||
name: 'library/nginx',
|
"os": "linux",
|
||||||
tag: '1.11.5',
|
"docker_version": "1.12.3",
|
||||||
architecture: 'amd64',
|
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
|
||||||
history: mockComp
|
"created": new Date("2016-11-08T22:41:15.912313785Z"),
|
||||||
};
|
"signature": null
|
||||||
|
|
||||||
let mockTags: Tag[] = [{
|
|
||||||
tag: '1.11.5',
|
|
||||||
manifest: mockManifest
|
|
||||||
}];
|
|
||||||
|
|
||||||
let mockSignatures: VerifiedSignature[] = [{
|
|
||||||
tag: '1.11.5',
|
|
||||||
hashes: {
|
|
||||||
sha256: 'fake'
|
|
||||||
}
|
}
|
||||||
}];
|
];
|
||||||
|
|
||||||
let mockSignatures2: VerifiedSignature[] = [{
|
|
||||||
tag: '1.11.15',
|
|
||||||
hashes: {
|
|
||||||
sha256: 'fake2'
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
const mockConfig: IServiceConfig = {
|
const mockConfig: IServiceConfig = {
|
||||||
repositoryBaseEndpoint: "/api/repositories/testing"
|
repositoryBaseEndpoint: "/api/repositories/testing"
|
||||||
|
@ -65,50 +48,4 @@ describe('TagService', () => {
|
||||||
expect(service).toBeTruthy();
|
expect(service).toBeTruthy();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should get tags with signed status[1] if signatures exists', async(inject([TagDefaultService], (service: TagService) => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
let spy1: jasmine.Spy = spyOn(service, '_getTags')
|
|
||||||
.and.returnValue(Promise.resolve(mockTags));
|
|
||||||
let spy2: jasmine.Spy = spyOn(service, '_getSignatures')
|
|
||||||
.and.returnValue(Promise.resolve(mockSignatures));
|
|
||||||
|
|
||||||
toPromise<Tag[]>(service.getTags('library/nginx'))
|
|
||||||
.then(tags => {
|
|
||||||
expect(tags).toBeTruthy();
|
|
||||||
expect(tags.length).toBe(1);
|
|
||||||
expect(tags[0].signed).toBe(1);
|
|
||||||
});
|
|
||||||
})));
|
|
||||||
|
|
||||||
it('should get tags with not-signed status[0] if signatures exists', async(inject([TagDefaultService], (service: TagService) => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
let spy1: jasmine.Spy = spyOn(service, '_getTags')
|
|
||||||
.and.returnValue(Promise.resolve(mockTags));
|
|
||||||
let spy2: jasmine.Spy = spyOn(service, '_getSignatures')
|
|
||||||
.and.returnValue(Promise.resolve(mockSignatures2));
|
|
||||||
|
|
||||||
toPromise<Tag[]>(service.getTags('library/nginx'))
|
|
||||||
.then(tags => {
|
|
||||||
expect(tags).toBeTruthy();
|
|
||||||
expect(tags.length).toBe(1);
|
|
||||||
expect(tags[0].signed).toBe(0);
|
|
||||||
});
|
|
||||||
})));
|
|
||||||
|
|
||||||
it('should get tags with default signed status[-1] if signatures not exist', async(inject([TagDefaultService], (service: TagService) => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
let spy1: jasmine.Spy = spyOn(service, '_getTags')
|
|
||||||
.and.returnValue(Promise.resolve(mockTags));
|
|
||||||
let spy2: jasmine.Spy = spyOn(service, '_getSignatures')
|
|
||||||
.and.returnValue(Promise.reject("Error"));
|
|
||||||
|
|
||||||
toPromise<Tag[]>(service.getTags('library/nginx'))
|
|
||||||
.then(tags => {
|
|
||||||
expect(tags).toBeTruthy();
|
|
||||||
expect(tags.length).toBe(1);
|
|
||||||
expect(tags[0].signed).toBe(-1);
|
|
||||||
});
|
|
||||||
})));
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -100,27 +100,7 @@ export class TagDefaultService extends TagService {
|
||||||
if (!repositoryName) {
|
if (!repositoryName) {
|
||||||
return Promise.reject("Bad argument");
|
return Promise.reject("Bad argument");
|
||||||
}
|
}
|
||||||
|
return this._getTags(repositoryName, queryParams);
|
||||||
return this._getTags(repositoryName, queryParams)
|
|
||||||
.then(tags => {
|
|
||||||
return this._getSignatures(repositoryName)
|
|
||||||
.then(signatures => {
|
|
||||||
tags.forEach(tag => {
|
|
||||||
let foundOne: VerifiedSignature | undefined = signatures.find(signature => signature.tag === tag.tag);
|
|
||||||
if (foundOne) {
|
|
||||||
tag.signed = 1;//Signed
|
|
||||||
} else {
|
|
||||||
tag.signed = 0;//Not signed
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return tags;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
tags.forEach(tag => tag.signed = -1);//No signature info
|
|
||||||
return tags;
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(error => Promise.reject(error))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteTag(repositoryName: string, tag: string): Observable<any> | Promise<Tag> | any {
|
public deleteTag(repositoryName: string, tag: string): Observable<any> | Promise<Tag> | any {
|
||||||
|
|
|
@ -4,7 +4,7 @@ export const TAG_TEMPLATE = `
|
||||||
<h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3>
|
<h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row col-md-12">
|
<div class="row col-md-12">
|
||||||
<textarea rows="3" (click)="selectAndCopy($event)">{{tagID}}</textarea>
|
<textarea rows="3" (click)="selectAndCopy($event)">{{digestId}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
@ -12,36 +12,39 @@ export const TAG_TEMPLATE = `
|
||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
<h2 class="sub-header-title">{{repoName}}</h2>
|
<h2 class="sub-header-title">{{repoName}}</h2>
|
||||||
<clr-datagrid>
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
<clr-dg-column *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'docker_version'">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'architecture'">{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.OS' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'os'">{{'REPOSITORY.OS' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
|
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
|
||||||
<clr-dg-action-overflow>
|
<clr-dg-action-overflow>
|
||||||
<button class="action-item" (click)="showTagID('tag', t)">{{'REPOSITORY.COPY_ID' | translate}}</button>
|
<button class="action-item" (click)="showDigestId(t)">{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button>
|
||||||
<button class="action-item" (click)="showTagID('parent', t)">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button>
|
|
||||||
<button class="action-item" [hidden]="!hasProjectAdminRole" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
<button class="action-item" [hidden]="!hasProjectAdminRole" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
<clr-dg-cell>{{t.name}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
|
<clr-dg-cell>docker pull {{registryUrl}}/{{repoName}}:{{t.name}}</clr-dg-cell>
|
||||||
<clr-dg-cell *ngIf="withNotary" [ngSwitch]="t.signed">
|
<clr-dg-cell *ngIf="withNotary" [ngSwitch]="t.signature !== null">
|
||||||
<clr-icon shape="check" *ngSwitchCase="1" style="color: #1D5100;"></clr-icon>
|
<clr-icon shape="check" *ngSwitchCase="true" style="color: #1D5100;"></clr-icon>
|
||||||
<clr-icon shape="close" *ngSwitchCase="0" style="color: #C92100;"></clr-icon>
|
<clr-icon shape="close" *ngSwitchCase="false" style="color: #C92100;"></clr-icon>
|
||||||
<a href="javascript:void(0)" *ngSwitchDefault role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
<a href="javascript:void(0)" *ngSwitchDefault role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||||
<clr-icon shape="help" style="color: #565656;" size="16"></clr-icon>
|
<clr-icon shape="help" style="color: #565656;" size="16"></clr-icon>
|
||||||
<span class="tooltip-content">{{'REPOSITORY.NOTARY_IS_UNDETERMINED' | translate}}</span>
|
<span class="tooltip-content">{{'REPOSITORY.NOTARY_IS_UNDETERMINED' | translate}}</span>
|
||||||
</a>
|
</a>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.author}}</clr-dg-cell>
|
<clr-dg-cell>{{t.author}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.created}}</clr-dg-cell>
|
<clr-dg-cell>{{t.created | date: 'short'}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.dockerVersion}}</clr-dg-cell>
|
<clr-dg-cell>{{t.docker_version}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.architecture}}</clr-dg-cell>
|
<clr-dg-cell>{{t.architecture}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.os}}</clr-dg-cell>
|
<clr-dg-cell>{{t.os}}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>{{tags ? tags.length : 0}} {{'REPOSITORY.ITEMS' | translate}}</clr-dg-footer>
|
<clr-dg-footer>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
|
||||||
|
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
</clr-datagrid>`;
|
</clr-datagrid>`;
|
|
@ -8,7 +8,7 @@ import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation
|
||||||
import { TagComponent } from './tag.component';
|
import { TagComponent } from './tag.component';
|
||||||
|
|
||||||
import { ErrorHandler } from '../error-handler/error-handler';
|
import { ErrorHandler } from '../error-handler/error-handler';
|
||||||
import { Tag, TagCompatibility, TagManifest, TagView } from '../service/interface';
|
import { Tag } from '../service/interface';
|
||||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
import { TagService, TagDefaultService } from '../service/tag.service';
|
import { TagService, TagDefaultService } from '../service/tag.service';
|
||||||
|
|
||||||
|
@ -19,21 +19,18 @@ describe('TagComponent (inline template)', ()=> {
|
||||||
let tagService: TagService;
|
let tagService: TagService;
|
||||||
let spy: jasmine.Spy;
|
let spy: jasmine.Spy;
|
||||||
|
|
||||||
let mockComp: TagCompatibility[] = [{
|
let mockTags: Tag[] = [
|
||||||
v1Compatibility: '{"architecture":"amd64","author":"NGINX Docker Maintainers \\"docker-maint@nginx.com\\"","config":{"Hostname":"6b3797ab1e90","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"443/tcp":{},"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.5-1~jessie"],"Cmd":["nginx","-g","daemon off;"],"ArgsEscaped":true,"Image":"sha256:47a33f0928217b307cf9f20920a0c6445b34ae974a60c1b4fe73b809379ad928","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":[],"Labels":{}},"container":"f1883a3fb44b0756a2a3b1e990736a44b1387183125351370042ce7bd9ffc338","container_config":{"Hostname":"6b3797ab1e90","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"443/tcp":{},"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.5-1~jessie"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\\"nginx\\" \\"-g\\" \\"daemon off;\\"]"],"ArgsEscaped":true,"Image":"sha256:47a33f0928217b307cf9f20920a0c6445b34ae974a60c1b4fe73b809379ad928","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":[],"Labels":{}},"created":"2016-11-08T22:41:15.912313785Z","docker_version":"1.12.3","id":"db3700426e6d7c1402667f42917109b2467dd49daa85d38ac99854449edc20b3","os":"linux","parent":"f3ef5f96caf99a18c6821487102c136b00e0275b1da0c7558d7090351f9d447e","throwaway":true}'
|
{
|
||||||
}];
|
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||||
let mockManifest: TagManifest = {
|
"name": "1.11.5",
|
||||||
schemaVersion: 1,
|
"architecture": "amd64",
|
||||||
name: 'library/nginx',
|
"os": "linux",
|
||||||
tag: '1.11.5',
|
"docker_version": "1.12.3",
|
||||||
architecture: 'amd64',
|
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
|
||||||
history: mockComp
|
"created": new Date("2016-11-08T22:41:15.912313785Z"),
|
||||||
};
|
"signature": null
|
||||||
|
}
|
||||||
let mockTags: Tag[] = [{
|
];
|
||||||
tag: '1.11.5',
|
|
||||||
manifest: mockManifest
|
|
||||||
}];
|
|
||||||
|
|
||||||
let config: IServiceConfig = {
|
let config: IServiceConfig = {
|
||||||
repositoryBaseEndpoint: '/api/repositories/testing'
|
repositoryBaseEndpoint: '/api/repositories/testing'
|
||||||
|
|
|
@ -26,30 +26,17 @@ import { Tag, SessionInfo } from '../service/interface';
|
||||||
import { TAG_TEMPLATE } from './tag.component.html';
|
import { TAG_TEMPLATE } from './tag.component.html';
|
||||||
import { TAG_STYLE } from './tag.component.css';
|
import { TAG_STYLE } from './tag.component.css';
|
||||||
|
|
||||||
import { toPromise } from '../utils';
|
import { toPromise, CustomComparator } from '../utils';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
/**
|
import { State, Comparator } from 'clarity-angular';
|
||||||
* Inteface for the tag view
|
|
||||||
*/
|
|
||||||
export interface TagView {
|
|
||||||
tag: string;
|
|
||||||
pullCommand: string;
|
|
||||||
signed: number;
|
|
||||||
author: string;
|
|
||||||
created: Date;
|
|
||||||
dockerVersion: string;
|
|
||||||
architecture: string;
|
|
||||||
os: string;
|
|
||||||
id: string;
|
|
||||||
parent: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hbr-tag',
|
selector: 'hbr-tag',
|
||||||
template: TAG_TEMPLATE,
|
template: TAG_TEMPLATE,
|
||||||
styles: [ TAG_STYLE ]
|
styles: [ TAG_STYLE ],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class TagComponent implements OnInit {
|
export class TagComponent implements OnInit {
|
||||||
|
|
||||||
|
@ -59,7 +46,7 @@ export class TagComponent implements OnInit {
|
||||||
|
|
||||||
hasProjectAdminRole: boolean;
|
hasProjectAdminRole: boolean;
|
||||||
|
|
||||||
tags: TagView[];
|
tags: Tag[];
|
||||||
|
|
||||||
registryUrl: string;
|
registryUrl: string;
|
||||||
withNotary: boolean;
|
withNotary: boolean;
|
||||||
|
@ -67,28 +54,17 @@ export class TagComponent implements OnInit {
|
||||||
|
|
||||||
showTagManifestOpened: boolean;
|
showTagManifestOpened: boolean;
|
||||||
manifestInfoTitle: string;
|
manifestInfoTitle: string;
|
||||||
tagID: string;
|
digestId: string;
|
||||||
staticBackdrop: boolean = true;
|
staticBackdrop: boolean = true;
|
||||||
closable: boolean = false;
|
closable: boolean = false;
|
||||||
|
|
||||||
|
createdComparator: Comparator<Tag> = new CustomComparator<Tag>('created', 'date');
|
||||||
|
|
||||||
|
loading: boolean = false;
|
||||||
|
|
||||||
@ViewChild('confirmationDialog')
|
@ViewChild('confirmationDialog')
|
||||||
confirmationDialog: ConfirmationDialogComponent;
|
confirmationDialog: ConfirmationDialogComponent;
|
||||||
|
|
||||||
get initTagView() {
|
|
||||||
return {
|
|
||||||
tag: '',
|
|
||||||
pullCommand: '',
|
|
||||||
signed: -1,
|
|
||||||
author: '',
|
|
||||||
created: new Date(),
|
|
||||||
dockerVersion: '',
|
|
||||||
architecture: '',
|
|
||||||
os: '',
|
|
||||||
id: '',
|
|
||||||
parent: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private errorHandler: ErrorHandler,
|
private errorHandler: ErrorHandler,
|
||||||
private tagService: TagService,
|
private tagService: TagService,
|
||||||
|
@ -99,14 +75,13 @@ export class TagComponent implements OnInit {
|
||||||
if (message &&
|
if (message &&
|
||||||
message.source === ConfirmationTargets.TAG
|
message.source === ConfirmationTargets.TAG
|
||||||
&& message.state === ConfirmationState.CONFIRMED) {
|
&& message.state === ConfirmationState.CONFIRMED) {
|
||||||
let tag = message.data;
|
let tag: Tag = message.data;
|
||||||
if (tag) {
|
if (tag) {
|
||||||
if (tag.signed) {
|
if (tag.signature) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
let tagName = tag.tag;
|
|
||||||
toPromise<number>(this.tagService
|
toPromise<number>(this.tagService
|
||||||
.deleteTag(this.repoName, tagName))
|
.deleteTag(this.repoName, tag.name))
|
||||||
.then(
|
.then(
|
||||||
response => {
|
response => {
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
|
@ -141,49 +116,34 @@ export class TagComponent implements OnInit {
|
||||||
|
|
||||||
retrieve() {
|
retrieve() {
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
|
this.loading = true;
|
||||||
toPromise<Tag[]>(this.tagService
|
toPromise<Tag[]>(this.tagService
|
||||||
.getTags(this.repoName))
|
.getTags(this.repoName))
|
||||||
.then(items => this.listTags(items))
|
.then(items => {
|
||||||
.catch(error => this.errorHandler.error(error));
|
this.tags = items;
|
||||||
}
|
this.loading = false;
|
||||||
|
})
|
||||||
listTags(tags: Tag[]): void {
|
.catch(error => {
|
||||||
tags.forEach(t => {
|
this.errorHandler.error(error);
|
||||||
let tag = this.initTagView;
|
this.loading = false;
|
||||||
tag.tag = t.tag;
|
});
|
||||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
|
||||||
tag.architecture = data['architecture'];
|
|
||||||
tag.author = data['author'];
|
|
||||||
if(!t.signed && t.signed !== 0) {
|
|
||||||
tag.signed = -1;
|
|
||||||
} else {
|
|
||||||
tag.signed = t.signed;
|
|
||||||
}
|
|
||||||
tag.created = data['created'];
|
|
||||||
tag.dockerVersion = data['docker_version'];
|
|
||||||
tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag;
|
|
||||||
tag.os = data['os'];
|
|
||||||
tag.id = data['id'];
|
|
||||||
tag.parent = data['parent'];
|
|
||||||
this.tags.push(tag);
|
|
||||||
});
|
|
||||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
||||||
setTimeout(()=>clearInterval(hnd), 1000);
|
setTimeout(()=>clearInterval(hnd), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTag(tag: TagView) {
|
deleteTag(tag: Tag) {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
|
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
|
||||||
if (tag.signed) {
|
if (tag.signature) {
|
||||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG_DENIED';
|
titleKey = 'REPOSITORY.DELETION_TITLE_TAG_DENIED';
|
||||||
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG_DENIED';
|
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG_DENIED';
|
||||||
buttons = ConfirmationButtons.CLOSE;
|
buttons = ConfirmationButtons.CLOSE;
|
||||||
content = 'notary -s https://' + this.registryUrl + ':4443 -d ~/.docker/trust remove -p ' + this.registryUrl + '/' + this.repoName + ' ' + tag.tag;
|
content = 'notary -s https://' + this.registryUrl + ':4443 -d ~/.docker/trust remove -p ' + this.registryUrl + '/' + this.repoName + ' ' + tag.name;
|
||||||
} else {
|
} else {
|
||||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG';
|
titleKey = 'REPOSITORY.DELETION_TITLE_TAG';
|
||||||
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG';
|
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG';
|
||||||
buttons = ConfirmationButtons.DELETE_CANCEL;
|
buttons = ConfirmationButtons.DELETE_CANCEL;
|
||||||
content = tag.tag;
|
content = tag.name;
|
||||||
}
|
}
|
||||||
let message = new ConfirmationMessage(
|
let message = new ConfirmationMessage(
|
||||||
titleKey,
|
titleKey,
|
||||||
|
@ -196,15 +156,10 @@ export class TagComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showTagID(type: string, tag: TagView) {
|
showDigestId(tag: Tag) {
|
||||||
if(tag) {
|
if(tag) {
|
||||||
if(type === 'tag') {
|
this.manifestInfoTitle = 'REPOSITORY.COPY_DIGEST_ID';
|
||||||
this.manifestInfoTitle = 'REPOSITORY.COPY_ID';
|
this.digestId = tag.digest;
|
||||||
this.tagID = tag.id;
|
|
||||||
} else if(type === 'parent') {
|
|
||||||
this.manifestInfoTitle = 'REPOSITORY.COPY_PARENT_ID';
|
|
||||||
this.tagID = tag.parent;
|
|
||||||
}
|
|
||||||
this.showTagManifestOpened = true;
|
this.showTagManifestOpened = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'rxjs/add/operator/toPromise';
|
||||||
import { RequestOptions, Headers } from '@angular/http';
|
import { RequestOptions, Headers } from '@angular/http';
|
||||||
import { RequestQueryParams } from './service/RequestQueryParams';
|
import { RequestQueryParams } from './service/RequestQueryParams';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
|
import { Comparator } from 'clarity-angular';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the different async channels to the Promise<T> type.
|
* Convert the different async channels to the Promise<T> type.
|
||||||
|
@ -85,4 +86,36 @@ export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClic
|
||||||
} else {
|
} else {
|
||||||
el.triggerEventHandler('click', eventObj);
|
el.triggerEventHandler('click', eventObj);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator for fields with specific type.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class CustomComparator<T> implements Comparator<T> {
|
||||||
|
|
||||||
|
fieldName: string;
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
constructor(fieldName: string, type: string) {
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
compare(a: {[key: string]: any| any[]}, b: {[key: string]: any| any[]}) {
|
||||||
|
let comp = 0;
|
||||||
|
if(a && b && a[this.fieldName] && b[this.fieldName]) {
|
||||||
|
let fieldA = a[this.fieldName];
|
||||||
|
let fieldB = b[this.fieldName];
|
||||||
|
switch(this.type) {
|
||||||
|
case "number":
|
||||||
|
comp = fieldB - fieldA;
|
||||||
|
break;
|
||||||
|
case "date":
|
||||||
|
comp = new Date(fieldB).getTime() - new Date(fieldA).getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user