mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-27 04:35:16 +01: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;
|
||||
if(this.targetNameHasChanged) {
|
||||
payload.name = this.target.name;
|
||||
delete payload.endpoint;
|
||||
}
|
||||
if (this.endpointHasChanged) {
|
||||
payload.endpoint = this.target.endpoint;
|
||||
|
@ -16,10 +16,10 @@ export const ENDPOINT_TEMPLATE: string = `
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'DESTINATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DESTINATION.URL' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'name'">{{'DESTINATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'endpoint'">{{'DESTINATION.URL' | 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-action-overflow>
|
||||
<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.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -116,8 +116,9 @@ describe('EndpointComponent (inline template)', () => {
|
||||
|
||||
it('should open create endpoint modal', async(() => {
|
||||
fixture.detectChanges();
|
||||
comp.editTarget(mockOne);
|
||||
fixture.whenStable().then(()=>{
|
||||
fixture.whenStable().then(()=>{
|
||||
fixture.detectChanges();
|
||||
comp.editTarget(mockOne);
|
||||
fixture.detectChanges();
|
||||
expect(comp.target.name).toEqual('target_01');
|
||||
});
|
||||
@ -125,7 +126,6 @@ describe('EndpointComponent (inline template)', () => {
|
||||
|
||||
it('should filter endpoints by keyword', async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(()=>{
|
||||
fixture.detectChanges();
|
||||
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_TEMPLATE } from './endpoint.component.html';
|
||||
|
||||
import { toPromise } from '../utils';
|
||||
import { toPromise, CustomComparator } from '../utils';
|
||||
|
||||
import { State, Comparator } from 'clarity-angular';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-endpoint',
|
||||
@ -55,6 +57,10 @@ export class EndpointComponent implements OnInit {
|
||||
targetName: string;
|
||||
subscription: Subscription;
|
||||
|
||||
loading: boolean = false;
|
||||
|
||||
creationTimeComparator: Comparator<Endpoint> = new CustomComparator<Endpoint>('creation_time', 'date');
|
||||
|
||||
get initEndpoint(): Endpoint {
|
||||
return {
|
||||
endpoint: "",
|
||||
@ -101,7 +107,7 @@ export class EndpointComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.targetName = '';
|
||||
this.retrieve('');
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -110,29 +116,34 @@ export class EndpointComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
retrieve(targetName: string): void {
|
||||
retrieve(): void {
|
||||
this.loading = true;
|
||||
toPromise<Endpoint[]>(this.endpointService
|
||||
.getEndpoints(targetName))
|
||||
.getEndpoints(this.targetName))
|
||||
.then(
|
||||
targets => {
|
||||
this.targets = targets || [];
|
||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
||||
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) {
|
||||
this.targetName = targetName;
|
||||
this.retrieve(targetName);
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
refreshTargets() {
|
||||
this.retrieve('');
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
reload($event: any) {
|
||||
this.targetName = '';
|
||||
this.retrieve('');
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
openModal() {
|
||||
|
@ -290,6 +290,7 @@ export const EN_US_LANG: any = {
|
||||
"INVALID_NAME": "Invalid endpoint name.",
|
||||
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
||||
"CREATION_TIME": "Creation Time",
|
||||
"OF": "of",
|
||||
"ITEMS": "item(s)",
|
||||
"CREATED_SUCCESS": "Created 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."
|
||||
},
|
||||
"REPOSITORY": {
|
||||
"COPY_ID": "Copy ID",
|
||||
"COPY_PARENT_ID": "Copy Parent ID",
|
||||
"COPY_DIGEST_ID": "Copy Digest ID",
|
||||
"DELETE": "Delete",
|
||||
"NAME": "Name",
|
||||
"TAGS_COUNT": "Tags",
|
||||
@ -324,6 +324,7 @@ export const EN_US_LANG: any = {
|
||||
"OS": "OS",
|
||||
"SHOW_DETAILS": "Show Details",
|
||||
"REPOSITORIES": "Repositories",
|
||||
"OF": "of",
|
||||
"ITEMS": "item(s)",
|
||||
"POP_REPOS": "Popular Repositories",
|
||||
"DELETED_REPO_SUCCESS": "Deleted repository successfully.",
|
||||
|
@ -290,6 +290,7 @@ export const ZH_CN_LANG: any = {
|
||||
"INVALID_NAME": "无效的目标名称。",
|
||||
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
||||
"CREATION_TIME": "创建时间",
|
||||
"OF": "共计",
|
||||
"ITEMS": "条记录",
|
||||
"CREATED_SUCCESS": "成功创建目标。",
|
||||
"UPDATED_SUCCESS": "成功更新目标。",
|
||||
@ -299,8 +300,7 @@ export const ZH_CN_LANG: any = {
|
||||
"FAILED_TO_DELETE_TARGET_IN_USED": "无法删除正在使用的目标。"
|
||||
},
|
||||
"REPOSITORY": {
|
||||
"COPY_ID": "复制ID",
|
||||
"COPY_PARENT_ID": "复制父级ID",
|
||||
"COPY_DIGEST_ID": "复制摘要ID",
|
||||
"DELETE": "删除",
|
||||
"NAME": "名称",
|
||||
"TAGS_COUNT": "标签数",
|
||||
@ -324,6 +324,7 @@ export const ZH_CN_LANG: any = {
|
||||
"OS": "操作系统",
|
||||
"SHOW_DETAILS": "显示详细",
|
||||
"REPOSITORIES": "镜像仓库",
|
||||
"OF": "共计",
|
||||
"ITEMS": "条记录",
|
||||
"POP_REPOS": "受欢迎的镜像仓库",
|
||||
"DELETED_REPO_SUCCESS": "成功删除镜像仓库。",
|
||||
|
@ -1,13 +1,13 @@
|
||||
export const LIST_REPLICATION_RULE_TEMPLATE: string = `
|
||||
<confirmation-dialog #toggleConfirmDialog (confirmAction)="toggleConfirm($event)"></confirmation-dialog>
|
||||
<confirmation-dialog #deletionConfirmDialog (confirmAction)="deletionConfirm($event)"></confirmation-dialog>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="projectless">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.LAST_START_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.ACTIVATION' | translate}}</clr-dg-column>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'project_name'" *ngIf="projectless">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'description'">{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'target_name'">{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'REPLICATION.LAST_START_TIME' | 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-action-overflow>
|
||||
<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>
|
||||
<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>
|
||||
{{ (p.enabled === 1 ? 'REPLICATION.ENABLED' : 'REPLICATION.DISABLED') | translate}}
|
||||
|
@ -25,9 +25,9 @@ import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
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';
|
||||
|
||||
@ -44,6 +44,8 @@ export class ListReplicationRuleComponent {
|
||||
@Input() projectless: boolean;
|
||||
@Input() selectedId: number | string;
|
||||
|
||||
@Input() loading: boolean = false;
|
||||
|
||||
@Output() reload = new EventEmitter<boolean>();
|
||||
@Output() selectOne = new EventEmitter<ReplicationRule>();
|
||||
@Output() editOne = new EventEmitter<ReplicationRule>();
|
||||
@ -55,11 +57,14 @@ export class ListReplicationRuleComponent {
|
||||
@ViewChild('deletionConfirmDialog')
|
||||
deletionConfirmDialog: ConfirmationDialogComponent;
|
||||
|
||||
startTimeComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>('start_time', 'date');
|
||||
enabledComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>('enabled', 'number');
|
||||
|
||||
constructor(
|
||||
private replicationService: ReplicationService,
|
||||
private translateService: TranslateService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private ref: ChangeDetectorRef) {
|
||||
private ref: ChangeDetectorRef) {
|
||||
setInterval(()=>ref.markForCheck(), 500);
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
export const LIST_REPOSITORY_TEMPLATE = `
|
||||
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
||||
<clr-dg-column>{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_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-action-overflow [hidden]="!hasProjectAdminRole">
|
||||
<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-row>
|
||||
<clr-dg-footer>
|
||||
{{(repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}}
|
||||
<clr-dg-pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
|
||||
{{pagination.totalItems}}{{'REPOSITORY.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>`;
|
@ -1,10 +1,12 @@
|
||||
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 { LIST_REPOSITORY_TEMPLATE } from './list-repository.component.html';
|
||||
|
||||
import { CustomComparator } from '../utils';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-list-repository',
|
||||
template: LIST_REPOSITORY_TEMPLATE,
|
||||
@ -21,6 +23,10 @@ export class ListRepositoryComponent {
|
||||
|
||||
pageOffset: number = 1;
|
||||
|
||||
pullCountComparator: Comparator<Repository> = new CustomComparator<Repository>('pull_count', 'number');
|
||||
|
||||
tagsCountComparator: Comparator<Repository> = new CustomComparator<Repository>('tags_count', 'number');
|
||||
|
||||
constructor(
|
||||
private ref: ChangeDetectorRef) {
|
||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
||||
|
@ -19,9 +19,11 @@ import {
|
||||
} from '../service/index';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { toPromise } from '../utils';
|
||||
import { toPromise, CustomComparator } from '../utils';
|
||||
import { LOG_TEMPLATE, LOG_STYLES } from './recent-log.template';
|
||||
|
||||
import { Comparator } from 'clarity-angular';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-log',
|
||||
styles: [LOG_STYLES],
|
||||
@ -35,6 +37,10 @@ export class RecentLogComponent implements OnInit {
|
||||
lines: number = 10; //Support 10, 25 and 50
|
||||
currentTerm: string;
|
||||
|
||||
loading: boolean;
|
||||
|
||||
opTimeComparator: Comparator<AccessLog> = new CustomComparator<AccessLog>('op_time', 'date');
|
||||
|
||||
constructor(
|
||||
private logService: AccessLogService,
|
||||
private errorHandler: ErrorHandler) { }
|
||||
@ -81,14 +87,17 @@ export class RecentLogComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.onGoing = true;
|
||||
this.loading = true;
|
||||
toPromise<AccessLog[]>(this.logService.getRecentLogs(this.lines))
|
||||
.then(response => {
|
||||
this.onGoing = false;
|
||||
this.loading = false;
|
||||
this.logsCache = response; //Keep the data
|
||||
this.recentLogs = this.logsCache.filter(log => log.username != "");//To display
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
this.loading = false;
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
@ -24,18 +24,18 @@ export const LOG_TEMPLATE: string = `
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.REPOSITORY_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.TAGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.OPERATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'AUDIT_LOG.TIMESTAMP' | translate}}</clr-dg-column>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'username'">{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'repo_name'">{{'AUDIT_LOG.REPOSITORY_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'repo_tag'">{{'AUDIT_LOG.TAGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'operation'">{{'AUDIT_LOG.OPERATION' | 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-cell>{{l.username}}</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.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-footer>{{ (recentLogs ? recentLogs.length : 0) }} {{'AUDIT_LOG.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
@ -20,7 +20,7 @@ export const REPLICATION_TEMPLATE: string = `
|
||||
</div>
|
||||
</div>
|
||||
<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 class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
@ -46,19 +46,19 @@ export const REPLICATION_TEMPLATE: string = `
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.END_TIME' | translate}}</clr-dg-column>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'repository'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'operation'">{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'REPLICATION.CREATION_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-row *clrDgItems="let j of jobs" [clrDgItem]='j'>
|
||||
<clr-dg-cell>{{j.repository}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.status}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.operation}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.creation_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.update_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{j.update_time | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<a href="/api/jobs/replication/{{j.id}}/log" target="_BLANK">
|
||||
<clr-icon shape="clipboard"></clr-icon>
|
||||
|
@ -24,7 +24,9 @@ import { ReplicationService } from '../service/replication.service';
|
||||
import { RequestQueryParams } from '../service/RequestQueryParams';
|
||||
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_STYLE } from './replication.component.css';
|
||||
@ -81,8 +83,10 @@ export class ReplicationComponent implements OnInit {
|
||||
changedRules: ReplicationRule[];
|
||||
initSelectedId: number | string;
|
||||
|
||||
rules: ReplicationRule[];
|
||||
jobs: ReplicationJob[];
|
||||
rules: ReplicationRule[];
|
||||
loading: boolean;
|
||||
|
||||
jobs: ReplicationJob[];
|
||||
|
||||
jobsTotalRecordCount: number;
|
||||
jobsTotalPage: number;
|
||||
@ -93,23 +97,28 @@ export class ReplicationComponent implements OnInit {
|
||||
@ViewChild(CreateEditRuleComponent)
|
||||
createEditPolicyComponent: CreateEditRuleComponent;
|
||||
|
||||
creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date');
|
||||
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('update_time', 'date');
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private replicationService: ReplicationService,
|
||||
private translateService: TranslateService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
ngOnInit() {
|
||||
if(!this.projectId) {
|
||||
this.errorHandler.warning('Project ID is unset.');
|
||||
}
|
||||
this.currentRuleStatus = this.ruleStatus[0];
|
||||
this.currentJobStatus = this.jobStatus[0];
|
||||
this.currentJobSearchOption = 0;
|
||||
|
||||
this.retrieveRules();
|
||||
}
|
||||
|
||||
retrieveRules(): void {
|
||||
this.loading = true;
|
||||
toPromise<ReplicationRule[]>(this.replicationService
|
||||
.getReplicationRules(this.projectId, this.search.ruleName))
|
||||
.then(response=>{
|
||||
@ -122,8 +131,12 @@ export class ReplicationComponent implements OnInit {
|
||||
this.search.ruleId = this.changedRules[0].id || '';
|
||||
this.fetchReplicationJobs();
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
).catch(error=>this.errorHandler.error(error));
|
||||
).catch(error=>{
|
||||
this.errorHandler.error(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
openModal(): void {
|
||||
@ -147,13 +160,15 @@ export class ReplicationComponent implements OnInit {
|
||||
params.set('repository', this.search.repoName);
|
||||
params.set('start_time', this.search.startTimestamp);
|
||||
params.set('end_time', this.search.endTimestamp);
|
||||
|
||||
|
||||
toPromise<ReplicationJob[]>(this.replicationService
|
||||
.getJobs(this.search.ruleId, params))
|
||||
.then(
|
||||
response=>{
|
||||
this.jobs = response;
|
||||
}).catch(error=>this.errorHandler.error(error));
|
||||
}).catch(error=>{
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
selectOneRule(rule: ReplicationRule) {
|
||||
|
@ -11,30 +11,6 @@ export interface Base {
|
||||
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
|
||||
*
|
||||
@ -59,10 +35,16 @@ export interface Repository extends Base {
|
||||
* @interface Tag
|
||||
* @extends {Base}
|
||||
*/
|
||||
|
||||
export interface Tag extends Base {
|
||||
tag: string;
|
||||
manifest: TagManifest;
|
||||
signed?: number; //May NOT exist
|
||||
digest: string;
|
||||
name: string;
|
||||
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');
|
||||
}
|
||||
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()
|
||||
.then(response => response)
|
||||
|
@ -4,41 +4,24 @@ import { TagService, TagDefaultService } from './tag.service';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
|
||||
import { Tag, TagCompatibility, TagManifest } from './interface';
|
||||
import { Tag } from './interface';
|
||||
|
||||
import { VerifiedSignature } from './tag.service';
|
||||
import { toPromise } from '../utils';
|
||||
|
||||
describe('TagService', () => {
|
||||
let mockComp: TagCompatibility[] = [{
|
||||
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}'
|
||||
}];
|
||||
let mockManifest: TagManifest = {
|
||||
schemaVersion: 1,
|
||||
name: 'library/nginx',
|
||||
tag: '1.11.5',
|
||||
architecture: 'amd64',
|
||||
history: mockComp
|
||||
};
|
||||
|
||||
let mockTags: Tag[] = [{
|
||||
tag: '1.11.5',
|
||||
manifest: mockManifest
|
||||
}];
|
||||
|
||||
let mockSignatures: VerifiedSignature[] = [{
|
||||
tag: '1.11.5',
|
||||
hashes: {
|
||||
sha256: 'fake'
|
||||
let mockTags: Tag[] = [
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
|
||||
"created": new Date("2016-11-08T22:41:15.912313785Z"),
|
||||
"signature": null
|
||||
}
|
||||
}];
|
||||
|
||||
let mockSignatures2: VerifiedSignature[] = [{
|
||||
tag: '1.11.15',
|
||||
hashes: {
|
||||
sha256: 'fake2'
|
||||
}
|
||||
}];
|
||||
];
|
||||
|
||||
const mockConfig: IServiceConfig = {
|
||||
repositoryBaseEndpoint: "/api/repositories/testing"
|
||||
@ -65,50 +48,4 @@ describe('TagService', () => {
|
||||
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) {
|
||||
return Promise.reject("Bad argument");
|
||||
}
|
||||
|
||||
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))
|
||||
return this._getTags(repositoryName, queryParams);
|
||||
}
|
||||
|
||||
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>
|
||||
<div class="modal-body">
|
||||
<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 class="modal-footer">
|
||||
@ -12,36 +12,39 @@ export const TAG_TEMPLATE = `
|
||||
</div>
|
||||
</clr-modal>
|
||||
<h2 class="sub-header-title">{{repoName}}</h2>
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.TAG' | 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>{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.OS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'docker_version'">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'architecture'">{{'REPOSITORY.ARCHITECTURE' | 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-action-overflow>
|
||||
<button class="action-item" (click)="showTagID('tag', t)">{{'REPOSITORY.COPY_ID' | translate}}</button>
|
||||
<button class="action-item" (click)="showTagID('parent', t)">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button>
|
||||
<button class="action-item" (click)="showDigestId(t)">{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button>
|
||||
<button class="action-item" [hidden]="!hasProjectAdminRole" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="withNotary" [ngSwitch]="t.signed">
|
||||
<clr-icon shape="check" *ngSwitchCase="1" style="color: #1D5100;"></clr-icon>
|
||||
<clr-icon shape="close" *ngSwitchCase="0" style="color: #C92100;"></clr-icon>
|
||||
<clr-dg-cell>{{t.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>docker pull {{registryUrl}}/{{repoName}}:{{t.name}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="withNotary" [ngSwitch]="t.signature !== null">
|
||||
<clr-icon shape="check" *ngSwitchCase="true" style="color: #1D5100;"></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">
|
||||
<clr-icon shape="help" style="color: #565656;" size="16"></clr-icon>
|
||||
<span class="tooltip-content">{{'REPOSITORY.NOTARY_IS_UNDETERMINED' | translate}}</span>
|
||||
</a>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.author}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.created}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.dockerVersion}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.created | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.docker_version}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.architecture}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.os}}</clr-dg-cell>
|
||||
</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>`;
|
@ -8,7 +8,7 @@ import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation
|
||||
import { TagComponent } from './tag.component';
|
||||
|
||||
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 { TagService, TagDefaultService } from '../service/tag.service';
|
||||
|
||||
@ -19,21 +19,18 @@ describe('TagComponent (inline template)', ()=> {
|
||||
let tagService: TagService;
|
||||
let spy: jasmine.Spy;
|
||||
|
||||
let mockComp: TagCompatibility[] = [{
|
||||
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}'
|
||||
}];
|
||||
let mockManifest: TagManifest = {
|
||||
schemaVersion: 1,
|
||||
name: 'library/nginx',
|
||||
tag: '1.11.5',
|
||||
architecture: 'amd64',
|
||||
history: mockComp
|
||||
};
|
||||
|
||||
let mockTags: Tag[] = [{
|
||||
tag: '1.11.5',
|
||||
manifest: mockManifest
|
||||
}];
|
||||
let mockTags: Tag[] = [
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
|
||||
"created": new Date("2016-11-08T22:41:15.912313785Z"),
|
||||
"signature": null
|
||||
}
|
||||
];
|
||||
|
||||
let config: IServiceConfig = {
|
||||
repositoryBaseEndpoint: '/api/repositories/testing'
|
||||
|
@ -26,30 +26,17 @@ import { Tag, SessionInfo } from '../service/interface';
|
||||
import { TAG_TEMPLATE } from './tag.component.html';
|
||||
import { TAG_STYLE } from './tag.component.css';
|
||||
|
||||
import { toPromise } from '../utils';
|
||||
import { toPromise, CustomComparator } from '../utils';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
import { State, Comparator } from 'clarity-angular';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-tag',
|
||||
template: TAG_TEMPLATE,
|
||||
styles: [ TAG_STYLE ]
|
||||
styles: [ TAG_STYLE ],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TagComponent implements OnInit {
|
||||
|
||||
@ -59,7 +46,7 @@ export class TagComponent implements OnInit {
|
||||
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
tags: TagView[];
|
||||
tags: Tag[];
|
||||
|
||||
registryUrl: string;
|
||||
withNotary: boolean;
|
||||
@ -67,28 +54,17 @@ export class TagComponent implements OnInit {
|
||||
|
||||
showTagManifestOpened: boolean;
|
||||
manifestInfoTitle: string;
|
||||
tagID: string;
|
||||
digestId: string;
|
||||
staticBackdrop: boolean = true;
|
||||
closable: boolean = false;
|
||||
|
||||
createdComparator: Comparator<Tag> = new CustomComparator<Tag>('created', 'date');
|
||||
|
||||
loading: boolean = false;
|
||||
|
||||
@ViewChild('confirmationDialog')
|
||||
confirmationDialog: ConfirmationDialogComponent;
|
||||
|
||||
get initTagView() {
|
||||
return {
|
||||
tag: '',
|
||||
pullCommand: '',
|
||||
signed: -1,
|
||||
author: '',
|
||||
created: new Date(),
|
||||
dockerVersion: '',
|
||||
architecture: '',
|
||||
os: '',
|
||||
id: '',
|
||||
parent: ''
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private tagService: TagService,
|
||||
@ -99,14 +75,13 @@ export class TagComponent implements OnInit {
|
||||
if (message &&
|
||||
message.source === ConfirmationTargets.TAG
|
||||
&& message.state === ConfirmationState.CONFIRMED) {
|
||||
let tag = message.data;
|
||||
let tag: Tag = message.data;
|
||||
if (tag) {
|
||||
if (tag.signed) {
|
||||
if (tag.signature) {
|
||||
return;
|
||||
} else {
|
||||
let tagName = tag.tag;
|
||||
toPromise<number>(this.tagService
|
||||
.deleteTag(this.repoName, tagName))
|
||||
.deleteTag(this.repoName, tag.name))
|
||||
.then(
|
||||
response => {
|
||||
this.retrieve();
|
||||
@ -141,49 +116,34 @@ export class TagComponent implements OnInit {
|
||||
|
||||
retrieve() {
|
||||
this.tags = [];
|
||||
this.loading = true;
|
||||
toPromise<Tag[]>(this.tagService
|
||||
.getTags(this.repoName))
|
||||
.then(items => this.listTags(items))
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
listTags(tags: Tag[]): void {
|
||||
tags.forEach(t => {
|
||||
let tag = this.initTagView;
|
||||
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);
|
||||
});
|
||||
.then(items => {
|
||||
this.tags = items;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorHandler.error(error);
|
||||
this.loading = false;
|
||||
});
|
||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
||||
setTimeout(()=>clearInterval(hnd), 1000);
|
||||
}
|
||||
|
||||
deleteTag(tag: TagView) {
|
||||
deleteTag(tag: Tag) {
|
||||
if (tag) {
|
||||
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
|
||||
if (tag.signed) {
|
||||
if (tag.signature) {
|
||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG_DENIED';
|
||||
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG_DENIED';
|
||||
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 {
|
||||
titleKey = 'REPOSITORY.DELETION_TITLE_TAG';
|
||||
summaryKey = 'REPOSITORY.DELETION_SUMMARY_TAG';
|
||||
buttons = ConfirmationButtons.DELETE_CANCEL;
|
||||
content = tag.tag;
|
||||
content = tag.name;
|
||||
}
|
||||
let message = new ConfirmationMessage(
|
||||
titleKey,
|
||||
@ -196,15 +156,10 @@ export class TagComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
showTagID(type: string, tag: TagView) {
|
||||
showDigestId(tag: Tag) {
|
||||
if(tag) {
|
||||
if(type === 'tag') {
|
||||
this.manifestInfoTitle = 'REPOSITORY.COPY_ID';
|
||||
this.tagID = tag.id;
|
||||
} else if(type === 'parent') {
|
||||
this.manifestInfoTitle = 'REPOSITORY.COPY_PARENT_ID';
|
||||
this.tagID = tag.parent;
|
||||
}
|
||||
this.manifestInfoTitle = 'REPOSITORY.COPY_DIGEST_ID';
|
||||
this.digestId = tag.digest;
|
||||
this.showTagManifestOpened = true;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'rxjs/add/operator/toPromise';
|
||||
import { RequestOptions, Headers } from '@angular/http';
|
||||
import { RequestQueryParams } from './service/RequestQueryParams';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Comparator } from 'clarity-angular';
|
||||
|
||||
/**
|
||||
* Convert the different async channels to the Promise<T> type.
|
||||
@ -85,4 +86,36 @@ export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClic
|
||||
} else {
|
||||
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