From e1df278ab52054e50a117e0b0b5f79dc69f7a9a4 Mon Sep 17 00:00:00 2001 From: kunw Date: Thu, 25 May 2017 19:14:35 +0800 Subject: [PATCH] Update components for pagination and sorting. --- .../create-edit-endpoint.component.ts | 1 + .../src/endpoint/endpoint.component.html.ts | 14 ++- .../src/endpoint/endpoint.component.spec.ts | 6 +- .../lib/src/endpoint/endpoint.component.ts | 27 +++-- src/ui_ng/lib/src/i18n/lang/en-us-lang.ts | 5 +- src/ui_ng/lib/src/i18n/lang/zh-cn-lang.ts | 5 +- .../list-replication-rule.component.html.ts | 16 +-- .../list-replication-rule.component.ts | 11 +- .../list-repository.component.html.ts | 11 +- .../list-repository.component.ts | 8 +- src/ui_ng/lib/src/log/recent-log.component.ts | 11 +- src/ui_ng/lib/src/log/recent-log.template.ts | 14 +-- .../replication/replication.component.html.ts | 18 +-- .../src/replication/replication.component.ts | 29 +++-- src/ui_ng/lib/src/service/interface.ts | 36 ++---- .../lib/src/service/repository.service.ts | 2 +- src/ui_ng/lib/src/service/tag.service.spec.ts | 87 ++------------- src/ui_ng/lib/src/service/tag.service.ts | 22 +--- src/ui_ng/lib/src/tag/tag.component.html.ts | 37 ++++--- src/ui_ng/lib/src/tag/tag.component.spec.ts | 29 +++-- src/ui_ng/lib/src/tag/tag.component.ts | 103 +++++------------- src/ui_ng/lib/src/utils.ts | 33 ++++++ 22 files changed, 233 insertions(+), 292 deletions(-) diff --git a/src/ui_ng/lib/src/create-edit-endpoint/create-edit-endpoint.component.ts b/src/ui_ng/lib/src/create-edit-endpoint/create-edit-endpoint.component.ts index 9ad2728ff..760c305b0 100644 --- a/src/ui_ng/lib/src/create-edit-endpoint/create-edit-endpoint.component.ts +++ b/src/ui_ng/lib/src/create-edit-endpoint/create-edit-endpoint.component.ts @@ -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; diff --git a/src/ui_ng/lib/src/endpoint/endpoint.component.html.ts b/src/ui_ng/lib/src/endpoint/endpoint.component.html.ts index 1b0b4a419..926d79083 100644 --- a/src/ui_ng/lib/src/endpoint/endpoint.component.html.ts +++ b/src/ui_ng/lib/src/endpoint/endpoint.component.html.ts @@ -16,10 +16,10 @@ export const ENDPOINT_TEMPLATE: string = `
- - {{'DESTINATION.NAME' | translate}} - {{'DESTINATION.URL' | translate}} - {{'DESTINATION.CREATION_TIME' | translate}} + + {{'DESTINATION.NAME' | translate}} + {{'DESTINATION.URL' | translate}} + {{'DESTINATION.CREATION_TIME' | translate}} @@ -29,7 +29,11 @@ export const ENDPOINT_TEMPLATE: string = ` {{t.endpoint}} {{t.creation_time | date: 'short'}} - {{ (targets ? targets.length : 0) }} {{'DESTINATION.ITEMS' | translate}} + + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}} + {{pagination.totalItems}} {{'DESTINATION.ITEMS' | translate}} + +
diff --git a/src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts b/src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts index 2b7af9f78..5e1ea2c27 100644 --- a/src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts +++ b/src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts @@ -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'); diff --git a/src/ui_ng/lib/src/endpoint/endpoint.component.ts b/src/ui_ng/lib/src/endpoint/endpoint.component.ts index cb5b4b282..93b66c5be 100644 --- a/src/ui_ng/lib/src/endpoint/endpoint.component.ts +++ b/src/ui_ng/lib/src/endpoint/endpoint.component.ts @@ -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 = new CustomComparator('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(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() { diff --git a/src/ui_ng/lib/src/i18n/lang/en-us-lang.ts b/src/ui_ng/lib/src/i18n/lang/en-us-lang.ts index 0771a03d3..9d8a8d81c 100644 --- a/src/ui_ng/lib/src/i18n/lang/en-us-lang.ts +++ b/src/ui_ng/lib/src/i18n/lang/en-us-lang.ts @@ -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.", diff --git a/src/ui_ng/lib/src/i18n/lang/zh-cn-lang.ts b/src/ui_ng/lib/src/i18n/lang/zh-cn-lang.ts index da51d3165..87a95c0ad 100644 --- a/src/ui_ng/lib/src/i18n/lang/zh-cn-lang.ts +++ b/src/ui_ng/lib/src/i18n/lang/zh-cn-lang.ts @@ -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": "成功删除镜像仓库。", diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts index e92dc8ea6..35efcab9c 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts @@ -1,13 +1,13 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = ` - - {{'REPLICATION.NAME' | translate}} - {{'REPLICATION.PROJECT' | translate}} - {{'REPLICATION.DESCRIPTION' | translate}} - {{'REPLICATION.DESTINATION_NAME' | translate}} - {{'REPLICATION.LAST_START_TIME' | translate}} - {{'REPLICATION.ACTIVATION' | translate}} + + {{'REPLICATION.NAME' | translate}} + {{'REPLICATION.PROJECT' | translate}} + {{'REPLICATION.DESCRIPTION' | translate}} + {{'REPLICATION.DESTINATION_NAME' | translate}} + {{'REPLICATION.LAST_START_TIME' | translate}} + {{'REPLICATION.ACTIVATION' | translate}} @@ -27,7 +27,7 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = ` {{p.target_name}} - - {{p.start_time}} + {{p.start_time | date: 'short'}} {{ (p.enabled === 1 ? 'REPLICATION.ENABLED' : 'REPLICATION.DISABLED') | translate}} diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts index b52ddade2..aa5e6c363 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts @@ -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(); @Output() selectOne = new EventEmitter(); @Output() editOne = new EventEmitter(); @@ -55,11 +57,14 @@ export class ListReplicationRuleComponent { @ViewChild('deletionConfirmDialog') deletionConfirmDialog: ConfirmationDialogComponent; + startTimeComparator: Comparator = new CustomComparator('start_time', 'date'); + enabledComparator: Comparator = new CustomComparator('enabled', 'number'); + constructor( private replicationService: ReplicationService, private translateService: TranslateService, private errorHandler: ErrorHandler, - private ref: ChangeDetectorRef) { + private ref: ChangeDetectorRef) { setInterval(()=>ref.markForCheck(), 500); } diff --git a/src/ui_ng/lib/src/list-repository/list-repository.component.html.ts b/src/ui_ng/lib/src/list-repository/list-repository.component.html.ts index e1fa0b5a0..65639f1de 100644 --- a/src/ui_ng/lib/src/list-repository/list-repository.component.html.ts +++ b/src/ui_ng/lib/src/list-repository/list-repository.component.html.ts @@ -1,8 +1,8 @@ export const LIST_REPOSITORY_TEMPLATE = ` - {{'REPOSITORY.NAME' | translate}} - {{'REPOSITORY.TAGS_COUNT' | translate}} - {{'REPOSITORY.PULL_COUNT' | translate}} + {{'REPOSITORY.NAME' | translate}} + {{'REPOSITORY.TAGS_COUNT' | translate}} + {{'REPOSITORY.PULL_COUNT' | translate}} @@ -12,7 +12,8 @@ export const LIST_REPOSITORY_TEMPLATE = ` {{r.pull_count}} - {{(repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}} - + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} + {{pagination.totalItems}}{{'REPOSITORY.ITEMS' | translate}} + `; \ No newline at end of file diff --git a/src/ui_ng/lib/src/list-repository/list-repository.component.ts b/src/ui_ng/lib/src/list-repository/list-repository.component.ts index 3a3e090d5..89c0e67d0 100644 --- a/src/ui_ng/lib/src/list-repository/list-repository.component.ts +++ b/src/ui_ng/lib/src/list-repository/list-repository.component.ts @@ -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 = new CustomComparator('pull_count', 'number'); + + tagsCountComparator: Comparator = new CustomComparator('tags_count', 'number'); + constructor( private ref: ChangeDetectorRef) { let hnd = setInterval(()=>ref.markForCheck(), 100); diff --git a/src/ui_ng/lib/src/log/recent-log.component.ts b/src/ui_ng/lib/src/log/recent-log.component.ts index 805c70bb6..d7f0a9ca7 100644 --- a/src/ui_ng/lib/src/log/recent-log.component.ts +++ b/src/ui_ng/lib/src/log/recent-log.component.ts @@ -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 = new CustomComparator('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(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); }); } diff --git a/src/ui_ng/lib/src/log/recent-log.template.ts b/src/ui_ng/lib/src/log/recent-log.template.ts index a1ba7bcef..88c112b36 100644 --- a/src/ui_ng/lib/src/log/recent-log.template.ts +++ b/src/ui_ng/lib/src/log/recent-log.template.ts @@ -24,18 +24,18 @@ export const LOG_TEMPLATE: string = `
- - {{'AUDIT_LOG.USERNAME' | translate}} - {{'AUDIT_LOG.REPOSITORY_NAME' | translate}} - {{'AUDIT_LOG.TAGS' | translate}} - {{'AUDIT_LOG.OPERATION' | translate}} - {{'AUDIT_LOG.TIMESTAMP' | translate}} + + {{'AUDIT_LOG.USERNAME' | translate}} + {{'AUDIT_LOG.REPOSITORY_NAME' | translate}} + {{'AUDIT_LOG.TAGS' | translate}} + {{'AUDIT_LOG.OPERATION' | translate}} + {{'AUDIT_LOG.TIMESTAMP' | translate}} {{l.username}} {{l.repo_name}} {{l.repo_tag}} {{l.operation}} - {{l.op_time}} + {{l.op_time | date: 'short'}} {{ (recentLogs ? recentLogs.length : 0) }} {{'AUDIT_LOG.ITEMS' | translate}} diff --git a/src/ui_ng/lib/src/replication/replication.component.html.ts b/src/ui_ng/lib/src/replication/replication.component.html.ts index f3ef8748b..b74186907 100644 --- a/src/ui_ng/lib/src/replication/replication.component.html.ts +++ b/src/ui_ng/lib/src/replication/replication.component.html.ts @@ -20,7 +20,7 @@ export const REPLICATION_TEMPLATE: string = `
- +
@@ -46,19 +46,19 @@ export const REPLICATION_TEMPLATE: string = `
- - {{'REPLICATION.NAME' | translate}} - {{'REPLICATION.STATUS' | translate}} - {{'REPLICATION.OPERATION' | translate}} - {{'REPLICATION.CREATION_TIME' | translate}} - {{'REPLICATION.END_TIME' | translate}} + + {{'REPLICATION.NAME' | translate}} + {{'REPLICATION.STATUS' | translate}} + {{'REPLICATION.OPERATION' | translate}} + {{'REPLICATION.CREATION_TIME' | translate}} + {{'REPLICATION.END_TIME' | translate}} {{'REPLICATION.LOGS' | translate}} {{j.repository}} {{j.status}} {{j.operation}} - {{j.creation_time}} - {{j.update_time}} + {{j.creation_time | date: 'short'}} + {{j.update_time | date: 'short'}} diff --git a/src/ui_ng/lib/src/replication/replication.component.ts b/src/ui_ng/lib/src/replication/replication.component.ts index 6a85e35a7..5b355612b 100644 --- a/src/ui_ng/lib/src/replication/replication.component.ts +++ b/src/ui_ng/lib/src/replication/replication.component.ts @@ -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 = new CustomComparator('creation_time', 'date'); + updateTimeComparator: Comparator = new CustomComparator('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(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(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) { diff --git a/src/ui_ng/lib/src/service/interface.ts b/src/ui_ng/lib/src/service/interface.ts index 4997006f7..bebad67b7 100644 --- a/src/ui_ng/lib/src/service/interface.ts +++ b/src/ui_ng/lib/src/service/interface.ts @@ -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; } /** diff --git a/src/ui_ng/lib/src/service/repository.service.ts b/src/ui_ng/lib/src/service/repository.service.ts index e5ddd6d4f..a4f7e1f2d 100644 --- a/src/ui_ng/lib/src/service/repository.service.ts +++ b/src/ui_ng/lib/src/service/repository.service.ts @@ -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) diff --git a/src/ui_ng/lib/src/service/tag.service.spec.ts b/src/ui_ng/lib/src/service/tag.service.spec.ts index c55e89914..9ccd122e5 100644 --- a/src/ui_ng/lib/src/service/tag.service.spec.ts +++ b/src/ui_ng/lib/src/service/tag.service.spec.ts @@ -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(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(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(service.getTags('library/nginx')) - .then(tags => { - expect(tags).toBeTruthy(); - expect(tags.length).toBe(1); - expect(tags[0].signed).toBe(-1); - }); - }))); - - }); diff --git a/src/ui_ng/lib/src/service/tag.service.ts b/src/ui_ng/lib/src/service/tag.service.ts index 2af47837f..dbce14f86 100644 --- a/src/ui_ng/lib/src/service/tag.service.ts +++ b/src/ui_ng/lib/src/service/tag.service.ts @@ -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 | Promise | any { diff --git a/src/ui_ng/lib/src/tag/tag.component.html.ts b/src/ui_ng/lib/src/tag/tag.component.html.ts index b3ed19645..5c07291ff 100644 --- a/src/ui_ng/lib/src/tag/tag.component.html.ts +++ b/src/ui_ng/lib/src/tag/tag.component.html.ts @@ -4,7 +4,7 @@ export const TAG_TEMPLATE = `

{{repoName}}

- - {{'REPOSITORY.TAG' | translate}} + + {{'REPOSITORY.TAG' | translate}} {{'REPOSITORY.PULL_COMMAND' | translate}} {{'REPOSITORY.SIGNED' | translate}} {{'REPOSITORY.AUTHOR' | translate}} - {{'REPOSITORY.CREATED' | translate}} - {{'REPOSITORY.DOCKER_VERSION' | translate}} - {{'REPOSITORY.ARCHITECTURE' | translate}} - {{'REPOSITORY.OS' | translate}} + {{'REPOSITORY.CREATED' | translate}} + {{'REPOSITORY.DOCKER_VERSION' | translate}} + {{'REPOSITORY.ARCHITECTURE' | translate}} + {{'REPOSITORY.OS' | translate}} - - + - {{t.tag}} - {{t.pullCommand}} - - - + {{t.name}} + docker pull {{registryUrl}}/{{repoName}}:{{t.name}} + + +
{{'REPOSITORY.NOTARY_IS_UNDETERMINED' | translate}}
{{t.author}} - {{t.created}} - {{t.dockerVersion}} + {{t.created | date: 'short'}} + {{t.docker_version}} {{t.architecture}} {{t.os}}
- {{tags ? tags.length : 0}} {{'REPOSITORY.ITEMS' | translate}} + + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} + {{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}} + +
`; \ No newline at end of file diff --git a/src/ui_ng/lib/src/tag/tag.component.spec.ts b/src/ui_ng/lib/src/tag/tag.component.spec.ts index e615cedca..a640d6909 100644 --- a/src/ui_ng/lib/src/tag/tag.component.spec.ts +++ b/src/ui_ng/lib/src/tag/tag.component.spec.ts @@ -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' diff --git a/src/ui_ng/lib/src/tag/tag.component.ts b/src/ui_ng/lib/src/tag/tag.component.ts index 0028805b6..95b292da9 100644 --- a/src/ui_ng/lib/src/tag/tag.component.ts +++ b/src/ui_ng/lib/src/tag/tag.component.ts @@ -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 = new CustomComparator('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(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(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; } } diff --git a/src/ui_ng/lib/src/utils.ts b/src/ui_ng/lib/src/utils.ts index 9b0b7067e..fcdf10dd0 100644 --- a/src/ui_ng/lib/src/utils.ts +++ b/src/ui_ng/lib/src/utils.ts @@ -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 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 implements Comparator { + + 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; + } } \ No newline at end of file