{{tag.name}}
@@ -61,9 +65,9 @@
- {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
- {{'TAG.OF' | translate}} {{pagination.totalItems}} {{'TAG.ITEMS' | translate}}
-
+ {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
+ {{'TAG.OF' | translate}} {{totalCount}} {{'TAG.ITEMS' | translate}}
+
diff --git a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.scss b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.scss
index 1a5c7ba4b..7615e3833 100644
--- a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.scss
+++ b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.scss
@@ -25,3 +25,9 @@
.position-ab {
position: absolute;
}
+.refresh-btn {
+ float: right;
+ margin-top: 15px;
+ margin-right: 20px;
+ cursor: pointer;
+}
diff --git a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.spec.ts b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.spec.ts
index 306b34483..c9617f46f 100644
--- a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.spec.ts
+++ b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.spec.ts
@@ -11,6 +11,8 @@ import { ArtifactService } from '../../../../../../ng-swagger-gen/services/artif
import { OperationService } from "../../../../../lib/components/operation/operation.service";
import { CURRENT_BASE_HREF } from "../../../../../lib/utils/utils";
import { USERSTATICPERMISSION, UserPermissionService, UserPermissionDefaultService } from '../../../../../lib/services';
+import { TagService } from '../../../../../../ng-swagger-gen/services/tag.service';
+import { delay } from 'rxjs/operators';
describe('ArtifactTagComponent', () => {
@@ -19,6 +21,10 @@ describe('ArtifactTagComponent', () => {
const mockErrorHandler = {
error: () => {}
};
+ const mockTagService = {
+ listTagsResponse: () => of({headers: null, body: []}).pipe(delay(0)),
+ listTags: () => of([]),
+ };
const mockArtifactService = {
createTag: () => of([]),
deleteTag: () => of(null),
@@ -29,6 +35,7 @@ describe('ArtifactTagComponent', () => {
let userPermissionService;
const permissions = [
{ resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE },
+ { resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.CREATE },
];
let mockHasDeleteImagePermission: boolean = true;
beforeEach(async(() => {
@@ -47,6 +54,7 @@ describe('ArtifactTagComponent', () => {
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: mockErrorHandler, useValue: ErrorHandler },
{ provide: ArtifactService, useValue: mockArtifactService },
+ { provide: TagService, useValue: mockTagService },
{ provide: UserPermissionService, useClass: UserPermissionDefaultService },
{ provide: OperationService },
]
@@ -62,6 +70,7 @@ describe('ArtifactTagComponent', () => {
.withArgs(component.projectId, permissions)
.and.returnValue(of([
mockHasDeleteImagePermission]));
+ component.artifactDetails = {id: 1};
fixture.detectChanges();
});
diff --git a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.ts b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.ts
index a1f5ad539..266482936 100644
--- a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.ts
+++ b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.ts
@@ -1,6 +1,6 @@
import { Component, OnInit, Input, ViewChild, Output, EventEmitter } from '@angular/core';
import { Observable, of, forkJoin } from 'rxjs';
-import { map, catchError } from 'rxjs/operators';
+import { map, catchError, finalize } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { NgForm } from '@angular/forms';
import { AVAILABLE_TIME } from "../../artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component";
@@ -16,11 +16,14 @@ import { operateChanges, OperateInfo, OperationState } from "../../../../../lib/
import { errorHandler } from "../../../../../lib/utils/shared/shared.utils";
import { ArtifactFront as Artifact } from "../artifact";
import { ArtifactService } from '../../../../../../ng-swagger-gen/services/artifact.service';
+import { TagService } from '../../../../../../ng-swagger-gen/services/tag.service';
import { Tag } from '../../../../../../ng-swagger-gen/models/tag';
import {
-
UserPermissionService, USERSTATICPERMISSION
} from "../../../../../lib/services";
+import { ClrDatagridStateInterface } from '@clr/angular';
+import { DEFAULT_PAGE_SIZE, calculatePage } from '../../../../../lib/utils/utils';
+
class InitTag {
name = "";
}
@@ -35,22 +38,29 @@ export class ArtifactTagComponent implements OnInit {
@Input() projectName: string;
@Input() projectId: number;
@Input() repositoryName: string;
- @Output() refreshArtifact = new EventEmitter();
newTagName = new InitTag();
newTagForm: NgForm;
@ViewChild("newTagForm", { static: true }) currentForm: NgForm;
selectedRow: Tag[] = [];
isTagNameExist = false;
newTagformShow = false;
- loading = false;
+ loading = true;
openTag = false;
availableTime = AVAILABLE_TIME;
@ViewChild("confirmationDialog", { static: false })
confirmationDialog: ConfirmationDialogComponent;
hasDeleteTagPermission: boolean;
+ hasCreateTagPermission: boolean;
+
+ totalCount: number = 0;
+ allTags: Tag[] = [];
+ currentTags: Tag[] = [];
+ pageSize: number = DEFAULT_PAGE_SIZE;
+ currentPage = 1;
constructor(
private operationService: OperationService,
private artifactService: ArtifactService,
+ private tagService: TagService,
private translateService: TranslateService,
private userPermissionService: UserPermissionService,
private errorHandlerService: ErrorHandler
@@ -58,13 +68,57 @@ export class ArtifactTagComponent implements OnInit {
) { }
ngOnInit() {
this.getImagePermissionRule(this.projectId);
+ this.getAllTags();
+ }
+ getCurrentArtifactTags(state: ClrDatagridStateInterface) {
+ if (!state || !state.page) {
+ return ;
+ }
+ let pageNumber: number = calculatePage(state);
+ if (pageNumber <= 0) { pageNumber = 1; }
+ let params: TagService.ListTagsParams = {
+ projectName: this.projectName,
+ repositoryName: this.repositoryName,
+ page: pageNumber,
+ withSignature: true,
+ withImmutableStatus: true,
+ pageSize: this.pageSize,
+ q: encodeURIComponent(`artifact_id=${this.artifactDetails.id}`)
+ };
+ this.tagService.listTagsResponse(params).pipe(finalize(() => {
+ this.loading = false;
+ })).subscribe(res => {
+ if (res.headers) {
+ let xHeader: string = res.headers.get("x-total-count");
+ if (xHeader) {
+ this.totalCount = Number.parseInt(xHeader);
+ }
+ }
+ this.currentTags = res.body;
+ }, error => {
+ this.errorHandlerService.error(error);
+ });
+ }
+ getAllTags() {
+ let params: TagService.ListTagsParams = {
+ projectName: this.projectName,
+ repositoryName: this.repositoryName
+ };
+ this.tagService.listTags(params).subscribe(res => {
+ this.allTags = res;
+ }, error => {
+ this.errorHandlerService.error(error);
+ });
}
getImagePermissionRule(projectId: number): void {
const permissions = [
- { resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE }
+ { resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE },
+ { resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.CREATE },
+
];
this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array) => {
this.hasDeleteTagPermission = results[0];
+ this.hasCreateTagPermission = results[1];
}, error => this.errorHandlerService.error(error));
}
@@ -84,11 +138,16 @@ export class ArtifactTagComponent implements OnInit {
reference: this.artifactDetails.digest,
tag: this.newTagName
};
+ this.loading = true;
this.artifactService.createTag(createTagParams).subscribe(res => {
this.newTagformShow = false;
this.newTagName = new InitTag();
- this.refreshArtifact.emit();
+ this.getAllTags();
+ this.currentPage = 1;
+ let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
+ this.getCurrentArtifactTags(st);
}, error => {
+ this.loading = false;
this.errorHandlerService.error(error);
});
}
@@ -124,12 +183,36 @@ export class ArtifactTagComponent implements OnInit {
tagList.forEach(tag => {
observableLists.push(this.delOperate(tag));
});
-
- forkJoin(...observableLists).subscribe((items) => {
+ this.loading = true;
+ forkJoin(...observableLists).subscribe((deleteResult) => {
// if delete one success refresh list
- if (items.some(item => !item)) {
- this.selectedRow = [];
- this.refreshArtifact.emit();
+ let deleteSuccessList = [];
+ let deleteErrorList = [];
+ deleteResult.forEach(result => {
+ if (!result) {
+ // delete success
+ deleteSuccessList.push(result);
+ } else {
+ deleteErrorList.push(result);
+ }
+ });
+ this.selectedRow = [];
+ if (deleteSuccessList.length === deleteResult.length) {
+ // all is success
+ this.currentPage = 1;
+ let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
+ this.getCurrentArtifactTags(st);
+ } else if (deleteErrorList.length === deleteResult.length) {
+ // all is error
+ this.loading = false;
+ this.errorHandlerService.error(deleteResult[deleteResult.length - 1].error);
+ } else {
+ // some artifact delete success but it has error delete things
+ this.errorHandlerService.error(deleteErrorList[deleteErrorList.length - 1].error);
+ // if delete one success refresh list
+ this.currentPage = 1;
+ let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
+ this.getCurrentArtifactTags(st);
}
});
}
@@ -167,8 +250,8 @@ export class ArtifactTagComponent implements OnInit {
existValid(name) {
this.isTagNameExist = false;
- if (this.artifactDetails.tags) {
- this.artifactDetails.tags.forEach(tag => {
+ if (this.allTags) {
+ this.allTags.forEach(tag => {
if (tag.name === name) {
this.isTagNameExist = true;
}
@@ -183,5 +266,10 @@ export class ArtifactTagComponent implements OnInit {
hasImmutableOnTag(): boolean {
return this.selectedRow.some((artifact) => artifact.immutable);
}
-
+ refresh() {
+ this.loading = true;
+ this.currentPage = 1;
+ let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
+ this.getCurrentArtifactTags(st);
+ }
}
diff --git a/src/portal/src/global.scss b/src/portal/src/global.scss
index 1ed5ce094..ef1255685 100644
--- a/src/portal/src/global.scss
+++ b/src/portal/src/global.scss
@@ -116,6 +116,12 @@ body {
button:focus {
outline: none;
}
+clr-dropdown {
+ div:focus {
+ outline: none;
+ }
+}
+
.w-100 {
width: 100% !important;
diff --git a/src/portal/src/lib/services/permission-static.ts b/src/portal/src/lib/services/permission-static.ts
index 4d040e3e6..2174e0e67 100644
--- a/src/portal/src/lib/services/permission-static.ts
+++ b/src/portal/src/lib/services/permission-static.ts
@@ -13,6 +13,7 @@ export const USERSTATICPERMISSION = {
"CREATE": "create",
"UPDATE": "update",
"DELETE": "delete",
+ "READ": "read",
"LIST": "list"
}
},
@@ -22,6 +23,7 @@ export const USERSTATICPERMISSION = {
"LIST": "list"
}
},
+ // to do remove
"REPLICATION": {
'KEY': 'replication',
'VALUE': {
@@ -31,6 +33,7 @@ export const USERSTATICPERMISSION = {
"LIST": "list",
}
},
+ // to do remove
"REPLICATION_JOB": {
'KEY': 'replication-job',
'VALUE': {
@@ -43,6 +46,7 @@ export const USERSTATICPERMISSION = {
"CREATE": "create",
"UPDATE": "update",
"DELETE": "delete",
+ "READ": "read",
"LIST": "list",
}
},
@@ -67,43 +71,47 @@ export const USERSTATICPERMISSION = {
"DELETE": "delete",
"LIST": "list",
"PUSH": "push",
+ "READ": "read",
"PULL": "pull",
}
},
+ "ARTIFACT": {
+ 'KEY': 'artifact',
+ 'VALUE': {
+ "CREATE": "create",
+ "DELETE": "delete",
+ "LIST": "list",
+ "READ": "read",
+ }
+ },
+ "ARTIFACT_ADDITION": {
+ 'KEY': 'artifact-addition',
+ 'VALUE': {
+ "READ": "read",
+ }
+ },
"REPOSITORY_TAG": {
- 'KEY': 'repository-tag',
+ 'KEY': 'tag',
'VALUE': {
"DELETE": "delete",
"LIST": "list",
+ "CREATE": "create"
}
},
"REPOSITORY_TAG_SCAN_JOB": {
- 'KEY': 'repository-tag-scan-job',
+ 'KEY': 'scan',
'VALUE': {
"CREATE": "create",
"READ": "read",
- "LIST": "list",
}
},
- "REPOSITORY_TAG_VULNERABILITY": {
- 'KEY': 'repository-tag-vulnerability',
- 'VALUE': {
- "LIST": "list",
- }
- },
- "REPOSITORY_TAG_LABEL": {
- 'KEY': 'repository-tag-label',
+ "REPOSITORY_ARTIFACT_LABEL": {
+ 'KEY': 'repository-artifact-label',
'VALUE': {
"CREATE": "create",
"DELETE": "delete",
}
},
- "REPOSITORY_TAG_MANIFEST": {
- 'KEY': 'repository-tag-manifest',
- 'VALUE': {
- "READ": "read",
- }
- },
"HELM_CHART": {
'KEY': 'helm-chart',
'VALUE': {
@@ -118,6 +126,7 @@ export const USERSTATICPERMISSION = {
'VALUE': {
"DELETE": "delete",
"LIST": "list",
+ "CREATE": "create",
"READ": "read",
}
},
@@ -146,8 +155,16 @@ export const USERSTATICPERMISSION = {
"DELETE": "delete",
"LIST": "list",
"READ": "read",
- "PULL": "pull",
- "PUSH": "push"
+ "OPERATE": "operate"
+ }
+ },
+ "IMMUTABLE_TAG": {
+ 'KEY': "immutable-tag",
+ 'VALUE': {
+ "CREATE": "create",
+ "UPDATE": "update",
+ "DELETE": "delete",
+ "LIST": "list",
}
},
"WEBHOOK": {
@@ -155,6 +172,8 @@ export const USERSTATICPERMISSION = {
"VALUE": {
"LIST": "list",
"READ": "read",
+ "CREATE": "create",
+ "UPDATE": "update",
}
},
"SCANNER": {
@@ -163,6 +182,15 @@ export const USERSTATICPERMISSION = {
"READ": "read",
"CREATE": "create"
}
+ },
+ "METADATA": {
+ "KEY": "metadata",
+ "VALUE": {
+ "READ": "read",
+ "CREATE": "create",
+ "UPDATE": "update",
+ "DELETE": "delete",
+ }
}
};