mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-28 21:37:31 +02:00
Add co-sign UI (#16155)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
b417e877b5
commit
2eda360d9d
@ -1,5 +1,5 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="breadcrumb" *ngIf="!withAdmiral">
|
<div class="breadcrumb">
|
||||||
<span class="back-icon"><</span>
|
<span class="back-icon"><</span>
|
||||||
<a (click)="goProBack()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
|
<a (click)="goProBack()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
|
||||||
<span class="back-icon"><</span>
|
<span class="back-icon"><</span>
|
||||||
@ -10,6 +10,29 @@
|
|||||||
<<a (click)="jumpDigest(i)" >{{digest | slice:0:15}}</a></span>
|
<<a (click)="jumpDigest(i)" >{{digest | slice:0:15}}</a></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<artifact-list [repoName]="repoName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole"
|
<section class="overview-section">
|
||||||
[projectId]="projectId" [memberRoleID]="projectMemberRoleId" [isGuest]="isGuest"></artifact-list>
|
<div class="title-wrapper">
|
||||||
|
<div class="title-block">
|
||||||
|
<h2 sub-header-title class="custom-h2" *ngIf="!artifactDigest">{{repoName}}</h2>
|
||||||
|
<h2 sub-header-title class="custom-h2" *ngIf="artifactDigest">{{artifactDigest | slice:0:15}}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="detail-section">
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<ul id="configTabs" class="nav" role="tablist">
|
||||||
|
<li role="presentation" class="nav-item" *ngIf="!artifactDigest">
|
||||||
|
<button id="repo-info" class="btn btn-link nav-link" aria-controls="info"
|
||||||
|
type="button" routerLinkActive="active" routerLink="./info-tab">{{'REPOSITORY.INFO' | translate}}</button>
|
||||||
|
</li>
|
||||||
|
<li role="presentation" class="nav-item">
|
||||||
|
<button id="repo-image" class="btn btn-link nav-link" aria-controls="image"
|
||||||
|
type="button" routerLinkActive="active" routerLink="./artifacts-tab">{{'REPOSITORY.ARTIFACTS' | translate}}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,39 +1,13 @@
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { ArtifactListPageComponent } from './artifact-list-page.component';
|
import { ArtifactListPageComponent } from './artifact-list-page.component';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { SessionService } from "../../../../../shared/services/session.service";
|
|
||||||
import { AppConfigService } from "../../../../../services/app-config.service";
|
|
||||||
import { ArtifactService } from "../artifact.service";
|
|
||||||
import { SharedTestingModule } from "../../../../../shared/shared.module";
|
import { SharedTestingModule } from "../../../../../shared/shared.module";
|
||||||
|
import { ArtifactListPageService } from './artifact-list-page.service';
|
||||||
|
|
||||||
describe('ArtifactListPageComponent', () => {
|
describe('ArtifactListPageComponent', () => {
|
||||||
let component: ArtifactListPageComponent;
|
let component: ArtifactListPageComponent;
|
||||||
let fixture: ComponentFixture<ArtifactListPageComponent>;
|
let fixture: ComponentFixture<ArtifactListPageComponent>;
|
||||||
const mockSessionService = {
|
|
||||||
getCurrentUser: () => { }
|
|
||||||
};
|
|
||||||
const mockAppConfigService = {
|
|
||||||
getConfig: () => {
|
|
||||||
return {
|
|
||||||
project_creation_restriction: "",
|
|
||||||
with_chartmuseum: "",
|
|
||||||
with_notary: "",
|
|
||||||
with_trivy: "",
|
|
||||||
with_admiral: "",
|
|
||||||
registry_url: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockRouter = {
|
|
||||||
navigate: () => { }
|
|
||||||
};
|
|
||||||
const mockArtifactService = {
|
|
||||||
triggerUploadArtifact: {
|
|
||||||
next: () => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockActivatedRoute = {
|
const mockActivatedRoute = {
|
||||||
RouterparamMap: of({ get: (key) => 'value' }),
|
RouterparamMap: of({ get: (key) => 'value' }),
|
||||||
snapshot: {
|
snapshot: {
|
||||||
@ -65,19 +39,13 @@ describe('ArtifactListPageComponent', () => {
|
|||||||
};
|
};
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
schemas: [
|
|
||||||
CUSTOM_ELEMENTS_SCHEMA
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
SharedTestingModule
|
SharedTestingModule
|
||||||
],
|
],
|
||||||
declarations: [ArtifactListPageComponent],
|
declarations: [ArtifactListPageComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SessionService, useValue: mockSessionService },
|
ArtifactListPageService,
|
||||||
{ provide: AppConfigService, useValue: mockAppConfigService },
|
|
||||||
{ provide: Router, useValue: mockRouter },
|
|
||||||
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||||
{ provide: ArtifactService, useValue: mockArtifactService },
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
@ -92,4 +60,10 @@ describe('ArtifactListPageComponent', () => {
|
|||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have two tabs', async () => {
|
||||||
|
await fixture.whenStable();
|
||||||
|
const tabs = fixture.nativeElement.querySelectorAll('.nav-item');
|
||||||
|
expect(tabs.length).toEqual(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,13 +11,10 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ArtifactListComponent } from "./artifact-list/artifact-list.component";
|
|
||||||
import { ArtifactService } from "../artifact.service";
|
|
||||||
import { AppConfigService } from "../../../../../services/app-config.service";
|
|
||||||
import { SessionService } from "../../../../../shared/services/session.service";
|
|
||||||
import { Project } from "../../../project";
|
import { Project } from "../../../project";
|
||||||
|
import { ArtifactListPageService } from "./artifact-list-page.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'artifact-list-page',
|
selector: 'artifact-list-page',
|
||||||
@ -26,29 +23,25 @@ import { Project } from "../../../project";
|
|||||||
})
|
})
|
||||||
export class ArtifactListPageComponent implements OnInit {
|
export class ArtifactListPageComponent implements OnInit {
|
||||||
|
|
||||||
projectId: number;
|
projectId: string;
|
||||||
projectName: string;
|
projectName: string;
|
||||||
projectMemberRoleId: number;
|
|
||||||
repoName: string;
|
repoName: string;
|
||||||
referArtifactNameArray: string[] = [];
|
referArtifactNameArray: string[] = [];
|
||||||
hasProjectAdminRole: boolean = false;
|
|
||||||
isGuest: boolean;
|
|
||||||
registryUrl: string;
|
|
||||||
|
|
||||||
@ViewChild(ArtifactListComponent)
|
|
||||||
repositoryComponent: ArtifactListComponent;
|
|
||||||
depth: string;
|
depth: string;
|
||||||
|
artifactDigest: string;
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private artifactService: ArtifactService,
|
private artifactListPageService: ArtifactListPageService) {
|
||||||
private appConfigService: AppConfigService,
|
|
||||||
private session: SessionService) {
|
|
||||||
this.route.params.subscribe(params => {
|
this.route.params.subscribe(params => {
|
||||||
this.depth = this.route.snapshot.params['depth'];
|
this.depth = this.route.snapshot.params['depth'];
|
||||||
if (this.depth) {
|
if (this.depth) {
|
||||||
const arr: string[] = this.depth.split('-');
|
const arr: string[] = this.depth.split('-');
|
||||||
this.referArtifactNameArray = arr.slice(0, arr.length - 1);
|
this.referArtifactNameArray = arr.slice(0, arr.length - 1);
|
||||||
|
this.artifactDigest = this.depth.split('-')[arr.length - 1];
|
||||||
|
} else {
|
||||||
|
this.referArtifactNameArray = [];
|
||||||
|
this.artifactDigest = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -58,28 +51,11 @@ export class ArtifactListPageComponent implements OnInit {
|
|||||||
let resolverData = this.route.snapshot.parent.data;
|
let resolverData = this.route.snapshot.parent.data;
|
||||||
if (resolverData) {
|
if (resolverData) {
|
||||||
this.projectName = (<Project>resolverData['projectResolver']).name;
|
this.projectName = (<Project>resolverData['projectResolver']).name;
|
||||||
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
|
||||||
this.isGuest = (<Project>resolverData['projectResolver']).current_user_role_id === 3;
|
|
||||||
this.projectMemberRoleId = (<Project>resolverData['projectResolver']).current_user_role_id;
|
|
||||||
}
|
}
|
||||||
this.repoName = this.route.snapshot.params['repo'];
|
this.repoName = this.route.snapshot.params['repo'];
|
||||||
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
this.artifactListPageService.init(+this.projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
get withNotary(): boolean {
|
|
||||||
return this.appConfigService.getConfig().with_notary;
|
|
||||||
}
|
|
||||||
get withAdmiral(): boolean {
|
|
||||||
return this.appConfigService.getConfig().with_admiral;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasSignedIn(): boolean {
|
|
||||||
return this.session.getCurrentUser() !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasChanges(): boolean {
|
|
||||||
return this.repositoryComponent.hasChanges();
|
|
||||||
}
|
|
||||||
watchGoBackEvt(projectId: string| number): void {
|
watchGoBackEvt(projectId: string| number): void {
|
||||||
this.router.navigate(["harbor", "projects", projectId, "repositories"]);
|
this.router.navigate(["harbor", "projects", projectId, "repositories"]);
|
||||||
}
|
}
|
||||||
@ -92,7 +68,7 @@ export class ArtifactListPageComponent implements OnInit {
|
|||||||
jumpDigest(index: number) {
|
jumpDigest(index: number) {
|
||||||
const arr: string[] = this.referArtifactNameArray.slice(0, index + 1 );
|
const arr: string[] = this.referArtifactNameArray.slice(0, index + 1 );
|
||||||
if ( arr && arr.length) {
|
if ( arr && arr.length) {
|
||||||
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repoName, "depth", arr.join('-')]);
|
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repoName, "artifacts-tab", "depth", arr.join('-')]);
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repoName]);
|
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repoName]);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import { inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ArtifactListPageService } from './artifact-list-page.service';
|
||||||
|
import { SharedTestingModule } from '../../../../../shared/shared.module';
|
||||||
|
|
||||||
|
describe('ArtifactListPageService', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedTestingModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ArtifactListPageService
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be initialized', inject([ArtifactListPageService], (service: ArtifactListPageService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
@ -0,0 +1,184 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { ClrLoadingState } from "@clr/angular";
|
||||||
|
import { ScanningResultService, UserPermissionService, USERSTATICPERMISSION } from "../../../../../shared/services";
|
||||||
|
import { LabelState } from "./artifact-list/artifact-list-tab/artifact-list-tab.component";
|
||||||
|
import { forkJoin, Observable } from "rxjs";
|
||||||
|
import { LabelService } from "ng-swagger-gen/services/label.service";
|
||||||
|
import { Label } from "ng-swagger-gen/models/label";
|
||||||
|
import { ErrorHandler } from "../../../../../shared/units/error-handler";
|
||||||
|
import { clone } from "../../../../../shared/units/utils";
|
||||||
|
|
||||||
|
const PAGE_SIZE: number = 100;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ArtifactListPageService {
|
||||||
|
private _scanBtnState: ClrLoadingState;
|
||||||
|
private _allLabels: LabelState[] = [];
|
||||||
|
imageStickLabels: LabelState[] = [];
|
||||||
|
imageFilterLabels: LabelState[] = [];
|
||||||
|
private _hasEnabledScanner: boolean = false;
|
||||||
|
private _hasAddLabelImagePermission: boolean = false;
|
||||||
|
private _hasRetagImagePermission: boolean = false;
|
||||||
|
private _hasDeleteImagePermission: boolean = false;
|
||||||
|
private _hasScanImagePermission: boolean = false;
|
||||||
|
|
||||||
|
constructor(private scanningService: ScanningResultService,
|
||||||
|
private labelService: LabelService,
|
||||||
|
private userPermissionService: UserPermissionService,
|
||||||
|
private errorHandlerService: ErrorHandler) {
|
||||||
|
|
||||||
|
}
|
||||||
|
resetClonedLabels() {
|
||||||
|
this.imageStickLabels = clone(this._allLabels);
|
||||||
|
this.imageFilterLabels = clone(this._allLabels);
|
||||||
|
}
|
||||||
|
getScanBtnState(): ClrLoadingState {
|
||||||
|
return this._scanBtnState;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEnabledScanner(): boolean {
|
||||||
|
return this._hasEnabledScanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAddLabelImagePermission(): boolean {
|
||||||
|
return this._hasAddLabelImagePermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRetagImagePermission(): boolean {
|
||||||
|
return this._hasRetagImagePermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDeleteImagePermission(): boolean {
|
||||||
|
return this._hasDeleteImagePermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasScanImagePermission(): boolean {
|
||||||
|
return this._hasScanImagePermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(projectId: number) {
|
||||||
|
this._getProjectScanner(projectId);
|
||||||
|
this._getPermissionRule(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getProjectScanner(projectId: number): void {
|
||||||
|
this._hasEnabledScanner = false;
|
||||||
|
this._scanBtnState = ClrLoadingState.LOADING;
|
||||||
|
this.scanningService.getProjectScanner(projectId)
|
||||||
|
.subscribe(response => {
|
||||||
|
if (response && "{}" !== JSON.stringify(response) && !response.disabled
|
||||||
|
&& response.health === "healthy") {
|
||||||
|
this._scanBtnState = ClrLoadingState.SUCCESS;
|
||||||
|
this._hasEnabledScanner = true;
|
||||||
|
} else {
|
||||||
|
this._scanBtnState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
}, error => {
|
||||||
|
this._scanBtnState = ClrLoadingState.ERROR;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getAllLabels(projectId: number): void {
|
||||||
|
// get all project labels
|
||||||
|
this.labelService.ListLabelsResponse({
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
page: 1,
|
||||||
|
scope: 'p',
|
||||||
|
projectId: projectId
|
||||||
|
}).subscribe(res => {
|
||||||
|
if (res.headers) {
|
||||||
|
const xHeader: string = res.headers.get("X-Total-Count");
|
||||||
|
const totalCount = parseInt(xHeader, 0);
|
||||||
|
let arr = res.body || [];
|
||||||
|
if (totalCount <= PAGE_SIZE) { // already gotten all project labels
|
||||||
|
if (arr && arr.length) {
|
||||||
|
arr.forEach(data => {
|
||||||
|
this._allLabels.push({'iconsShow': false, 'label': data, 'show': true});
|
||||||
|
});
|
||||||
|
this.resetClonedLabels();
|
||||||
|
}
|
||||||
|
} else { // get all the project labels in specified times
|
||||||
|
const times: number = Math.ceil(totalCount / PAGE_SIZE);
|
||||||
|
const observableList: Observable<Label[]>[] = [];
|
||||||
|
for (let i = 2; i <= times; i++) {
|
||||||
|
observableList.push(this.labelService.ListLabels({
|
||||||
|
page: i,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
scope: 'p',
|
||||||
|
projectId: projectId
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
this._handleLabelRes(observableList, arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// get all global labels
|
||||||
|
this.labelService.ListLabelsResponse({
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
page: 1,
|
||||||
|
scope: 'g',
|
||||||
|
}).subscribe(res => {
|
||||||
|
if (res.headers) {
|
||||||
|
const xHeader: string = res.headers.get("X-Total-Count");
|
||||||
|
const totalCount = parseInt(xHeader, 0);
|
||||||
|
let arr = res.body || [];
|
||||||
|
if (totalCount <= PAGE_SIZE) { // already gotten all global labels
|
||||||
|
if (arr && arr.length) {
|
||||||
|
arr.forEach(data => {
|
||||||
|
this._allLabels.push({'iconsShow': false, 'label': data, 'show': true});
|
||||||
|
});
|
||||||
|
this.resetClonedLabels();
|
||||||
|
}
|
||||||
|
} else { // get all the global labels in specified times
|
||||||
|
const times: number = Math.ceil(totalCount / PAGE_SIZE);
|
||||||
|
const observableList: Observable<Label[]>[] = [];
|
||||||
|
for (let i = 2; i <= times; i++) {
|
||||||
|
observableList.push(this.labelService.ListLabels({
|
||||||
|
page: i,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
scope: 'g',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
this._handleLabelRes(observableList, arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private _handleLabelRes(observableList: Observable<Label[]>[], arr: Label[]) {
|
||||||
|
forkJoin(observableList).subscribe(response => {
|
||||||
|
if (response && response.length) {
|
||||||
|
response.forEach(item => {
|
||||||
|
arr = arr.concat(item);
|
||||||
|
});
|
||||||
|
arr.forEach(data => {
|
||||||
|
this._allLabels.push({'iconsShow': false, 'label': data, 'show': true});
|
||||||
|
});
|
||||||
|
this.resetClonedLabels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getPermissionRule(projectId: number): void {
|
||||||
|
const permissions = [
|
||||||
|
{
|
||||||
|
resource: USERSTATICPERMISSION.REPOSITORY_ARTIFACT_LABEL.KEY,
|
||||||
|
action: USERSTATICPERMISSION.REPOSITORY_ARTIFACT_LABEL.VALUE.CREATE
|
||||||
|
},
|
||||||
|
{resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.PULL},
|
||||||
|
{resource: USERSTATICPERMISSION.ARTIFACT.KEY, action: USERSTATICPERMISSION.ARTIFACT.VALUE.DELETE},
|
||||||
|
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE},
|
||||||
|
];
|
||||||
|
this.userPermissionService.hasProjectPermissions(projectId, permissions).subscribe((results: Array<boolean>) => {
|
||||||
|
this._hasAddLabelImagePermission = results[0];
|
||||||
|
this._hasRetagImagePermission = results[1];
|
||||||
|
this._hasDeleteImagePermission = results[2];
|
||||||
|
this._hasScanImagePermission = results[3];
|
||||||
|
// only has label permission
|
||||||
|
if (this._hasAddLabelImagePermission) {
|
||||||
|
this._getAllLabels(projectId);
|
||||||
|
}
|
||||||
|
}, error => this.errorHandlerService.error(error));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<section id="info" role="tabpanel" aria-labelledby="repo-info">
|
||||||
|
<form #repoInfoForm="ngForm">
|
||||||
|
<div id="info-edit-button">
|
||||||
|
<button class="btn " [disabled]="editing || !hasProjectAdminRole || onSaving " (click)="editInfo()">
|
||||||
|
<clr-icon shape="pencil" size="16"></clr-icon> {{'BUTTON.EDIT' | translate}}
|
||||||
|
</button>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 1024" preserveAspectRatio="xMinYMin" class="markdown">
|
||||||
|
<path d="M950.154 192H73.846C33.127 192 0 225.12699999999995 0 265.846v492.308C0 798.875 33.127 832 73.846 832h876.308c40.721 0 73.846-33.125 73.846-73.846V265.846C1024 225.12699999999995 990.875 192 950.154 192zM576 703.875L448 704V512l-96 123.077L256 512v192H128V320h128l96 128 96-128 128-0.125V703.875zM767.091 735.875L608 512h96V320h128v192h96L767.091 735.875z" />
|
||||||
|
</svg>
|
||||||
|
<span>{{ 'REPOSITORY.MARKDOWN' | translate }}</span>
|
||||||
|
</div>
|
||||||
|
<div id="no-editing" *ngIf="!editing">
|
||||||
|
<div class="loading" *ngIf="loading">
|
||||||
|
<span class="spinner spinner-inline"></span>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="!loading">
|
||||||
|
<div *ngIf="!imageInfo" class="no-info-div">
|
||||||
|
<p>{{'REPOSITORY.NO_INFO' | translate }}<p>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="imageInfo" class="info-div">
|
||||||
|
<div class="info-pre" [innerHTML]="imageInfo | markdown"></div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="editing">
|
||||||
|
<textarea id="info-edit-textarea" class="clr-textarea w-100" rows="5" name="info-edit-textarea"
|
||||||
|
[(ngModel)]="imageInfo"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="" *ngIf="editing">
|
||||||
|
<button id="edit-save" class="btn btn-primary" [disabled]="!hasChanges()" (click)="saveInfo()">{{'BUTTON.SAVE' | translate}}</button>
|
||||||
|
<button id="edit-cancel" class="btn" (click)="cancelInfo()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
<confirmation-dialog #confirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
|
||||||
|
</form>
|
||||||
|
</section>
|
@ -1,28 +1,3 @@
|
|||||||
.option-right {
|
|
||||||
padding-right: 16px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-back {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-block {
|
|
||||||
border-right: 2px solid #cccccc;
|
|
||||||
margin-right: 6px;
|
|
||||||
display: inline-flex;
|
|
||||||
padding: 6px 6px 6px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-block {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-name {
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-info-div {
|
.no-info-div {
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px;
|
border: 1px;
|
||||||
@ -35,7 +10,17 @@
|
|||||||
border: 1px;
|
border: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: #CCCCCC;
|
border-color: #CCCCCC;
|
||||||
padding: 0px 12px 24px 12px;
|
padding: 0 12px 24px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
height: 3rem;
|
||||||
|
border: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #CCCCCC;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-pre {
|
.info-pre {
|
||||||
@ -57,12 +42,3 @@
|
|||||||
fill: gray;
|
fill: gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#images-container {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
harbor-tag {
|
|
||||||
position: relative;
|
|
||||||
top: 24px;
|
|
||||||
}
|
|
@ -0,0 +1,37 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync, } from '@angular/core/testing';
|
||||||
|
import { of } from "rxjs";
|
||||||
|
import { ArtifactInfoComponent } from './artifact-info.component';
|
||||||
|
import { SharedTestingModule } from 'src/app/shared/shared.module';
|
||||||
|
import { RepositoryService } from 'ng-swagger-gen/services/repository.service';
|
||||||
|
|
||||||
|
describe('ArtifactInfoComponent', () => {
|
||||||
|
|
||||||
|
let compRepo: ArtifactInfoComponent;
|
||||||
|
let fixture: ComponentFixture<ArtifactInfoComponent>;
|
||||||
|
let FakedRepositoryService = {
|
||||||
|
updateRepository: () => of(null),
|
||||||
|
getRepository: () => of({description: ''})
|
||||||
|
};
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedTestingModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ArtifactInfoComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: RepositoryService, useValue: FakedRepositoryService},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ArtifactInfoComponent);
|
||||||
|
compRepo = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('should create', () => {
|
||||||
|
expect(compRepo).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { RepositoryService } from 'ng-swagger-gen/services/repository.service';
|
||||||
|
import { ConfirmationMessage } from 'src/app/base/global-confirmation-dialog/confirmation-message';
|
||||||
|
import { ConfirmationAcknowledgement } from 'src/app/base/global-confirmation-dialog/confirmation-state-message';
|
||||||
|
import { Project } from 'src/app/base/project/project';
|
||||||
|
import { ConfirmationDialogComponent } from 'src/app/shared/components/confirmation-dialog/confirmation-dialog.component';
|
||||||
|
import { ConfirmationState, ConfirmationTargets } from 'src/app/shared/entities/shared.const';
|
||||||
|
import { ErrorHandler } from 'src/app/shared/units/error-handler/error-handler';
|
||||||
|
import { dbEncodeURIComponent } from 'src/app/shared/units/utils';
|
||||||
|
import { finalize } from "rxjs/operators";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'artifact-info',
|
||||||
|
templateUrl: './artifact-info.component.html',
|
||||||
|
styleUrls: ['./artifact-info.component.scss']
|
||||||
|
})
|
||||||
|
export class ArtifactInfoComponent implements OnInit {
|
||||||
|
projectName: string;
|
||||||
|
repoName: string;
|
||||||
|
hasProjectAdminRole: boolean = false;
|
||||||
|
onSaving: boolean = false;
|
||||||
|
loading: boolean = false;
|
||||||
|
editing: boolean = false;
|
||||||
|
imageInfo: string;
|
||||||
|
orgImageInfo: string;
|
||||||
|
@ViewChild('confirmationDialog')
|
||||||
|
confirmationDlg: ConfirmationDialogComponent;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private errorHandler: ErrorHandler,
|
||||||
|
private repositoryService: RepositoryService,
|
||||||
|
private translate: TranslateService,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.repoName = this.activatedRoute.snapshot?.parent?.params['repo'];
|
||||||
|
let resolverData = this.activatedRoute.snapshot?.parent?.parent?.data;
|
||||||
|
if (resolverData) {
|
||||||
|
this.projectName = (<Project>resolverData['projectResolver']).name;
|
||||||
|
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||||
|
}
|
||||||
|
this.retrieve();
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieve() {
|
||||||
|
let params: RepositoryService.GetRepositoryParams = {
|
||||||
|
projectName: this.projectName,
|
||||||
|
repositoryName: dbEncodeURIComponent(this.repoName),
|
||||||
|
};
|
||||||
|
this.loading = true;
|
||||||
|
this.repositoryService.getRepository(params)
|
||||||
|
.pipe(finalize(() => this.loading = false))
|
||||||
|
.subscribe(response => {
|
||||||
|
this.orgImageInfo = response.description;
|
||||||
|
this.imageInfo = response.description;
|
||||||
|
}, error => this.errorHandler.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.retrieve();
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChanges() {
|
||||||
|
return this.imageInfo !== this.orgImageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.imageInfo = this.orgImageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
editInfo() {
|
||||||
|
this.editing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveInfo() {
|
||||||
|
if (!this.hasChanges()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onSaving = true;
|
||||||
|
let params: RepositoryService.UpdateRepositoryParams = {
|
||||||
|
repositoryName: dbEncodeURIComponent(this.repoName),
|
||||||
|
repository: {description: this.imageInfo},
|
||||||
|
projectName: this.projectName,
|
||||||
|
};
|
||||||
|
this.repositoryService.updateRepository(params)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.onSaving = false;
|
||||||
|
this.translate.get('CONFIG.SAVE_SUCCESS').subscribe((res: string) => {
|
||||||
|
this.errorHandler.info(res);
|
||||||
|
});
|
||||||
|
this.editing = false;
|
||||||
|
this.refresh();
|
||||||
|
}, error => {
|
||||||
|
this.onSaving = false;
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelInfo() {
|
||||||
|
let msg = new ConfirmationMessage(
|
||||||
|
'CONFIG.CONFIRM_TITLE',
|
||||||
|
'CONFIG.CONFIRM_SUMMARY',
|
||||||
|
'',
|
||||||
|
{},
|
||||||
|
ConfirmationTargets.CONFIG
|
||||||
|
);
|
||||||
|
this.confirmationDlg.open(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmCancel(ack: ConfirmationAcknowledgement): void {
|
||||||
|
this.editing = false;
|
||||||
|
if (ack && ack.source === ConfirmationTargets.CONFIG &&
|
||||||
|
ack.state === ConfirmationState.CONFIRMED) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@
|
|||||||
<div class="row flex-items-xs-right rightPos">
|
<div class="row flex-items-xs-right rightPos">
|
||||||
<div id="filterArea" *ngIf="!depth">
|
<div id="filterArea" *ngIf="!depth">
|
||||||
<div class='filterLabelPiece' *ngIf="(openLabelFilterPiece &&filterByType ==='labels')"
|
<div class='filterLabelPiece' *ngIf="(openLabelFilterPiece &&filterByType ==='labels')"
|
||||||
[style.left.px]='96'>
|
[style.left.px]='110'>
|
||||||
<hbr-label-piece *ngIf="showlabel" [hidden]='!filterOneLabel' [label]="filterOneLabel"
|
<hbr-label-piece *ngIf="showlabel" [hidden]='!filterOneLabel' [label]="filterOneLabel"
|
||||||
[labelWidth]="130"></hbr-label-piece>
|
[labelWidth]="130"></hbr-label-piece>
|
||||||
</div>
|
</div>
|
||||||
@ -59,19 +59,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="label-filter-panel" *ngIf="!withAdmiral" [hidden]="!(openLabelFilterPanel&&filterByType==='labels')">
|
<div class="label-filter-panel" [hidden]="!(openLabelFilterPanel&&filterByType==='labels')">
|
||||||
<a class="filterClose" (click)="closeFilter()">×</a>
|
<a class="filterClose" (click)="closeFilter()">×</a>
|
||||||
<label
|
<label
|
||||||
class="filterLabelHeader filter-dark">{{'REPOSITORY.FILTER_ARTIFACT_BY_LABEL' | translate}}</label>
|
class="filterLabelHeader filter-dark">{{'REPOSITORY.FILTER_ARTIFACT_BY_LABEL' | translate}}</label>
|
||||||
<div class="form-group"><input clrInput type="text" placeholder="Filter labels"
|
<div class="form-group mb-05"><input clrInput type="text" placeholder="Filter labels"
|
||||||
[(ngModel)]="filterName" (keyup)="handleInputFilter()"></div>
|
[(ngModel)]="filterName" (keyup)="handleInputFilter()"></div>
|
||||||
<div [hidden]='imageFilterLabels.length' class="no-labels">{{'LABEL.NO_LABELS' | translate }}
|
<div [hidden]='artifactListPageService?.imageFilterLabels.length' class="no-labels">{{'LABEL.NO_LABELS' | translate }}
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]='!imageFilterLabels.length' class="has-label">
|
<div [hidden]='!artifactListPageService?.imageFilterLabels.length' class="has-label">
|
||||||
<button type="button" class="labelBtn" *ngFor='let label of imageFilterLabels'
|
<button type="button" class="labelBtn" *ngFor='let label of artifactListPageService?.imageFilterLabels'
|
||||||
[hidden]="!label.show" (click)="rightFilterLabel(label)">
|
[hidden]="!label.show" (click)="rightFilterLabel(label)">
|
||||||
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'></clr-icon>
|
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'></clr-icon>
|
||||||
<div class='labelDiv'>
|
<div class='labelDiv top-3-px'>
|
||||||
<hbr-label-piece [label]="label.label" [labelWidth]="160"></hbr-label-piece>
|
<hbr-label-piece [label]="label.label" [labelWidth]="160"></hbr-label-piece>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@ -87,7 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="clrDgRefresh($event)" class="datagrid-top" [class.embeded-datagrid]="isEmbedded"
|
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="clrDgRefresh($event)" class="datagrid-top"
|
||||||
[(clrDgSelected)]="selectedRow">
|
[(clrDgSelected)]="selectedRow">
|
||||||
<clr-dg-action-bar>
|
<clr-dg-action-bar>
|
||||||
<button id="scan-btn" [clrLoading]="scanBtnState" type="button" class="btn btn-secondary scan-btn"
|
<button id="scan-btn" [clrLoading]="scanBtnState" type="button" class="btn btn-secondary scan-btn"
|
||||||
@ -111,7 +111,7 @@
|
|||||||
<div class="action-dropdown-item no-border" aria-label="copy digest" clrDropdownItem
|
<div class="action-dropdown-item no-border" aria-label="copy digest" clrDropdownItem
|
||||||
[clrDisabled]="!(selectedRow.length==1&& !depth)" (click)="showDigestId()">
|
[clrDisabled]="!(selectedRow.length==1&& !depth)" (click)="showDigestId()">
|
||||||
{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</div>
|
{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</div>
|
||||||
<clr-dropdown *ngIf="!withAdmiral">
|
<clr-dropdown>
|
||||||
<button class="action-dropdown-item" clrDropdownTrigger
|
<button class="action-dropdown-item" clrDropdownTrigger
|
||||||
[disabled]="!canAddLabel()||!hasAddLabelImagePermission ||depth || inprogress"
|
[disabled]="!canAddLabel()||!hasAddLabelImagePermission ||depth || inprogress"
|
||||||
(click)="addLabels()">
|
(click)="addLabels()">
|
||||||
@ -124,11 +124,11 @@
|
|||||||
<div class="form-group filter-label-input"><input clrInput type="text"
|
<div class="form-group filter-label-input"><input clrInput type="text"
|
||||||
placeholder="Filter labels" [(ngModel)]="stickName"
|
placeholder="Filter labels" [(ngModel)]="stickName"
|
||||||
(keyup)="handleStickInputFilter()"></div>
|
(keyup)="handleStickInputFilter()"></div>
|
||||||
<div [hidden]='imageStickLabels.length' class="no-labels">
|
<div [hidden]='artifactListPageService?.imageStickLabels.length' class="no-labels">
|
||||||
{{'LABEL.NO_LABELS' | translate }}</div>
|
{{'LABEL.NO_LABELS' | translate }}</div>
|
||||||
<div [hidden]='!imageStickLabels.length' class="has-label">
|
<div [hidden]='!artifactListPageService?.imageStickLabels.length' class="has-label">
|
||||||
<button type="button" class="dropdown-item" clrDropdownItem
|
<button type="button" class="dropdown-item" clrDropdownItem
|
||||||
*ngFor='let label of imageStickLabels' [hidden]='!label.show'
|
*ngFor='let label of artifactListPageService?.imageStickLabels' [hidden]='!label.show'
|
||||||
(click)="stickLabel(label)">
|
(click)="stickLabel(label)">
|
||||||
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'>
|
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'>
|
||||||
</clr-icon>
|
</clr-icon>
|
||||||
@ -142,7 +142,7 @@
|
|||||||
|
|
||||||
</clr-dropdown-menu>
|
</clr-dropdown-menu>
|
||||||
</clr-dropdown>
|
</clr-dropdown>
|
||||||
<div class="action-dropdown-item" aria-label="retag" *ngIf="!withAdmiral"
|
<div class="action-dropdown-item" aria-label="retag"
|
||||||
[clrDisabled]="!(selectedRow.length===1)|| !hasRetagImagePermission||depth" (click)="retag()"
|
[clrDisabled]="!(selectedRow.length===1)|| !hasRetagImagePermission||depth" (click)="retag()"
|
||||||
clrDropdownItem>{{'REPOSITORY.RETAG' | translate}}</div>
|
clrDropdownItem>{{'REPOSITORY.RETAG' | translate}}</div>
|
||||||
<div class="action-dropdown-item" clrDropdownItem *ngIf="hasDeleteImagePermission"
|
<div class="action-dropdown-item" clrDropdownItem *ngIf="hasDeleteImagePermission"
|
||||||
@ -155,24 +155,25 @@
|
|||||||
|
|
||||||
<clr-dg-column class="flex-max-width" [clrDgSortBy]="'digest'">{{'REPOSITORY.ARTIFACTS_COUNT' | translate}}
|
<clr-dg-column class="flex-max-width" [clrDgSortBy]="'digest'">{{'REPOSITORY.ARTIFACTS_COUNT' | translate}}
|
||||||
</clr-dg-column>
|
</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
<clr-dg-column class="pull-command-column">{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column *ngIf="depth">{{'REPOSITORY.PLATFORM' | translate}}</clr-dg-column>
|
<clr-dg-column *ngIf="depth">{{'REPOSITORY.PLATFORM' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column class="w-rem-4">{{'REPOSITORY.TAGS' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'REPOSITORY.TAGS' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column class="co-signed-column">{{'ACCESSORY.CO_SIGNED' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.VULNERABILITY' | translate}}</clr-dg-column>
|
<clr-dg-column class="vul-column">{{'REPOSITORY.VULNERABILITY' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'ARTIFACT.ANNOTATION' | translate}}</clr-dg-column>
|
<clr-dg-column class="annotations-column">{{'ARTIFACT.ANNOTATION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column *ngIf="!withAdmiral">{{'REPOSITORY.LABELS' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'REPOSITORY.LABELS' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="pushComparator">{{'REPOSITORY.PUSH_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="pushComparator">{{'REPOSITORY.PUSH_TIME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="pullComparator">{{'REPOSITORY.PULL_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="pullComparator">{{'REPOSITORY.PULL_TIME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-placeholder>{{'ARTIFACT.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
<clr-dg-placeholder>{{'ARTIFACT.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||||
<clr-dg-row *ngFor="let artifact of artifactList" [clrDgItem]="artifact" >
|
<clr-dg-row *ngFor="let artifact of artifactList" [clrDgItem]="artifact" >
|
||||||
<clr-dg-cell class="truncated flex-max-width">
|
<clr-dg-cell class="flex-max-width truncated">
|
||||||
<div class="cell white-normal">
|
<div class="cell white-normal">
|
||||||
<div class="artifact-icon clr-display-inline-block" *ngIf="artifact.icon">
|
<div class="artifact-icon clr-display-inline-block" *ngIf="artifact.icon">
|
||||||
<img *ngIf="getIcon(artifact.icon)" class="artifact-icon" [title]="artifact.type"
|
<img *ngIf="getIcon(artifact.icon)" class="artifact-icon" [title]="artifact.type"
|
||||||
[src]="getIcon(artifact.icon)" (error)="showDefaultIcon($event)" />
|
[src]="getIcon(artifact.icon)" (error)="showDefaultIcon($event)" />
|
||||||
</div>
|
</div>
|
||||||
<a href="javascript:void(0)" class="max-width-100 margin-left-5" (click)="goIntoArtifactSummaryPage(artifact)"
|
<a href="javascript:void(0)" class="digest margin-left-5" (click)="goIntoArtifactSummaryPage(artifact)"
|
||||||
title="{{artifact.digest}}">
|
title="{{artifact.digest}}">
|
||||||
{{ artifact.digest | slice:0:15}}</a>
|
{{ artifact.digest | slice:0:15}}</a>
|
||||||
<clr-tooltip *ngIf="artifact?.references && artifact?.references?.length">
|
<clr-tooltip *ngIf="artifact?.references && artifact?.references?.length">
|
||||||
@ -189,7 +190,7 @@
|
|||||||
</clr-tooltip>
|
</clr-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell class="truncated" title="{{artifact.pullCommand}}">
|
<clr-dg-cell title="{{artifact.pullCommand}}">
|
||||||
<hbr-copy-input *ngIf="artifact.pullCommand" #copyInput (onCopyError)="onCpError($event)" iconMode="true" defaultValue="{{artifact.pullCommand}}"></hbr-copy-input>
|
<hbr-copy-input *ngIf="artifact.pullCommand" #copyInput (onCopyError)="onCpError($event)" iconMode="true" defaultValue="{{artifact.pullCommand}}"></hbr-copy-input>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell *ngIf="depth">
|
<clr-dg-cell *ngIf="depth">
|
||||||
@ -197,20 +198,15 @@
|
|||||||
{{artifact.platform?.os}}/{{artifact.platform?.architecture}}{{artifact.platform?.variant?'/'+artifact.platform?.variant: ''}}
|
{{artifact.platform?.os}}/{{artifact.platform?.architecture}}{{artifact.platform?.variant?'/'+artifact.platform?.variant: ''}}
|
||||||
</div>
|
</div>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell class="w-rem-4">
|
<clr-dg-cell class="center">
|
||||||
<div *ngIf="artifact.tags" class="truncated width-p-100">
|
<div *ngIf="artifact.tags" class="truncated width-p-100">
|
||||||
<clr-tooltip class="width-p-100">
|
<clr-tooltip class="width-p-100">
|
||||||
<div clrTooltipTrigger class="level-border">
|
<div clrTooltipTrigger class="center">
|
||||||
<div>
|
<div class="center">
|
||||||
<div class="inner truncated ">
|
<span #tagName class="truncated">{{artifact?.tags[0]?.name}}</span>
|
||||||
<span>
|
|
||||||
{{artifact?.tags[0]?.name}}
|
|
||||||
</span>
|
|
||||||
<span class="eslip"
|
<span class="eslip"
|
||||||
*ngIf="artifact?.tags?.length>1">...</span>
|
*ngIf="artifact?.tags?.length>1 && isOverflow()">...</span>
|
||||||
<span *ngIf="artifact?.tags?.length>1" > ({{artifact?.tagNumber}})</span>
|
<span *ngIf="artifact?.tags?.length>1 || isOverflow()">({{artifact?.tagNumber}})</span>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<clr-tooltip-content [clrPosition]="'top-right'" class="lg" [clrSize]="'lg'" *clrIfOpen>
|
<clr-tooltip-content [clrPosition]="'top-right'" class="lg" [clrSize]="'lg'" *clrIfOpen>
|
||||||
@ -219,8 +215,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="left tag-header-color">
|
<th class="left tag-header-color">
|
||||||
{{'REPOSITORY.TAGS' | translate | uppercase}}</th>
|
{{'REPOSITORY.TAGS' | translate | uppercase}}</th>
|
||||||
<th *ngIf="withNotary" class="left tag-header-color">
|
<th *ngIf="withNotary" class="left tag-header-color">{{'ACCESSORY.NOTARY_SIGNED' | translate | uppercase}}</th>
|
||||||
{{'REPOSITORY.SIGNED' | translate | uppercase}}</th>
|
|
||||||
<th class="left tag-header-color">
|
<th class="left tag-header-color">
|
||||||
{{'REPOSITORY.PULL_TIME' | translate | uppercase}}</th>
|
{{'REPOSITORY.PULL_TIME' | translate | uppercase}}</th>
|
||||||
<th class="left tag-header-color">
|
<th class="left tag-header-color">
|
||||||
@ -253,6 +248,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span *ngIf="artifact.coSigned === 'checking'" class="spinner spinner-inline ml-2"></span>
|
||||||
|
<clr-icon shape="check-circle" *ngIf="artifact.coSigned === 'true'" size="20" class="signed"></clr-icon>
|
||||||
|
<clr-icon shape="times-circle" *ngIf="artifact.coSigned === 'false'" size="16" class="color-red"></clr-icon>
|
||||||
|
</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
{{artifact.size?sizeTransform(artifact.size+''): ""}}
|
{{artifact.size?sizeTransform(artifact.size+''): ""}}
|
||||||
@ -290,7 +290,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell *ngIf="!withAdmiral">
|
<clr-dg-cell>
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<hbr-label-piece *ngIf="artifact.labels?.length" [label]="artifact.labels[0]" [labelWidth]="90">
|
<hbr-label-piece *ngIf="artifact.labels?.length" [label]="artifact.labels[0]" [labelWidth]="90">
|
||||||
</hbr-label-piece>
|
</hbr-label-piece>
|
||||||
@ -316,6 +316,7 @@
|
|||||||
<div class="cell">
|
<div class="cell">
|
||||||
{{artifact.pull_time === availableTime ? "" : (artifact.pull_time| harborDatetime: 'short')}}</div>
|
{{artifact.pull_time === availableTime ? "" : (artifact.pull_time| harborDatetime: 'short')}}</div>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
|
<sub-accessories (deleteAccessory)="deleteAccessory($event)" [projectName]="projectName" [repositoryName]="repoName" *ngIf="artifact?.accessories?.length" [total]="artifact?.accessoryNumber" [accessories]="artifact?.accessories" [clrIfExpanded]="false" ngProjectAs="clr-dg-row-detail"></sub-accessories>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
<clr-dg-pagination #pagination [clrDgTotalItems]="totalCount" [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize">
|
<clr-dg-pagination #pagination [clrDgTotalItems]="totalCount" [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize">
|
||||||
|
@ -73,6 +73,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.truncated {
|
.truncated {
|
||||||
|
width: 100px;
|
||||||
|
line-height: 20px;
|
||||||
|
height: 20px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -104,8 +107,8 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
line-height: 1;
|
line-height: 1.3rem;
|
||||||
height: 1.2rem;
|
height: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu input {
|
.dropdown-menu input {
|
||||||
@ -142,7 +145,7 @@
|
|||||||
.labelDiv {
|
.labelDiv {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 34px;
|
left: 34px;
|
||||||
top: 5px;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trigger-item hbr-label-piece {
|
.trigger-item hbr-label-piece {
|
||||||
@ -306,11 +309,10 @@ clr-datagrid {
|
|||||||
.white-normal {
|
.white-normal {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
.max-width-100 {
|
.digest {
|
||||||
width: 128px;
|
width: 128px;
|
||||||
// overflow: hidden;
|
flex-grow:0;
|
||||||
// white-space: nowrap;
|
flex-shrink:0;
|
||||||
// text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
.max-width-38 {
|
.max-width-38 {
|
||||||
max-width: 38px !important;
|
max-width: 38px !important;
|
||||||
@ -324,11 +326,12 @@ clr-datagrid {
|
|||||||
.artifact-icon {
|
.artifact-icon {
|
||||||
width: 0.8rem;
|
width: 0.8rem;
|
||||||
height: 0.8rem;
|
height: 0.8rem;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
.width-p-100 {
|
.width-p-100 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.lg {
|
.lg {
|
||||||
width: 450px;
|
width: 550px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.w-rem-4 {
|
.w-rem-4 {
|
||||||
@ -339,6 +342,7 @@ clr-datagrid {
|
|||||||
}
|
}
|
||||||
.tag-body-color {
|
.tag-body-color {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
|
max-width: 120px;
|
||||||
}
|
}
|
||||||
.table {
|
.table {
|
||||||
.tag-tr {
|
.tag-tr {
|
||||||
@ -414,3 +418,28 @@ clr-datagrid {
|
|||||||
.margin-left-5 {
|
.margin-left-5 {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
.pull-command-column {
|
||||||
|
width: 6rem !important;
|
||||||
|
}
|
||||||
|
.co-signed-column {
|
||||||
|
width: 6rem !important;
|
||||||
|
}
|
||||||
|
.vul-column {
|
||||||
|
width: 11rem !important;
|
||||||
|
}
|
||||||
|
.annotations-column {
|
||||||
|
width: 5rem !important;
|
||||||
|
}
|
||||||
|
.signed {
|
||||||
|
color: #00d40f;
|
||||||
|
}
|
||||||
|
.mb-05 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.top-3-px {
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
@ -28,6 +28,9 @@ import { Tag } from "../../../../../../../../../ng-swagger-gen/models/tag";
|
|||||||
import { SharedTestingModule } from "../../../../../../../shared/shared.module";
|
import { SharedTestingModule } from "../../../../../../../shared/shared.module";
|
||||||
import { LabelService } from "../../../../../../../../../ng-swagger-gen/services/label.service";
|
import { LabelService } from "../../../../../../../../../ng-swagger-gen/services/label.service";
|
||||||
import { Registry } from "../../../../../../../../../ng-swagger-gen/models/registry";
|
import { Registry } from "../../../../../../../../../ng-swagger-gen/models/registry";
|
||||||
|
import { AppConfigService } from "../../../../../../../services/app-config.service";
|
||||||
|
import { ArtifactListPageService } from '../../artifact-list-page.service';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
|
||||||
describe("ArtifactListTabComponent (inline template)", () => {
|
describe("ArtifactListTabComponent (inline template)", () => {
|
||||||
|
|
||||||
@ -290,6 +293,38 @@ describe("ArtifactListTabComponent (inline template)", () => {
|
|||||||
return of(res).pipe(delay(0));
|
return of(res).pipe(delay(0));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const mockedAppConfigService = {
|
||||||
|
getConfig() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockedArtifactListPageService = {
|
||||||
|
imageStickLabels: [],
|
||||||
|
imageFilterLabels: [],
|
||||||
|
resetClonedLabels() {
|
||||||
|
},
|
||||||
|
getScanBtnState(): ClrLoadingState {
|
||||||
|
return ClrLoadingState.DEFAULT;
|
||||||
|
},
|
||||||
|
hasEnabledScanner(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
hasAddLabelImagePermission(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
hasRetagImagePermission(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
hasDeleteImagePermission(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
hasScanImagePermission(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
}
|
||||||
|
};
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -306,7 +341,9 @@ describe("ArtifactListTabComponent (inline template)", () => {
|
|||||||
CopyInputComponent
|
CopyInputComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ArtifactDefaultService,
|
{ provide: ArtifactListPageService, useValue: mockedArtifactListPageService },
|
||||||
|
{ provide: ArtifactService, useClass: ArtifactDefaultService },
|
||||||
|
{ provide: AppConfigService, useValue: mockedAppConfigService },
|
||||||
{ provide: Router, useValue: mockRouter },
|
{ provide: Router, useValue: mockRouter },
|
||||||
{ provide: ArtifactService, useValue: mockNewArtifactService },
|
{ provide: ArtifactService, useValue: mockNewArtifactService },
|
||||||
{ provide: ProjectService, useClass: ProjectDefaultService },
|
{ provide: ProjectService, useClass: ProjectDefaultService },
|
||||||
@ -325,12 +362,7 @@ describe("ArtifactListTabComponent (inline template)", () => {
|
|||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.projectId = 1;
|
comp.projectId = 1;
|
||||||
comp.repoName = "library/nginx";
|
comp.repoName = "library/nginx";
|
||||||
comp.hasDeleteImagePermission = true;
|
|
||||||
comp.hasScanImagePermission = true;
|
|
||||||
comp.hasSignedIn = true;
|
|
||||||
comp.registryUrl = "http://registry.testing.com";
|
comp.registryUrl = "http://registry.testing.com";
|
||||||
comp.withNotary = false;
|
|
||||||
comp.withAdmiral = false;
|
|
||||||
let labelService: LabelService;
|
let labelService: LabelService;
|
||||||
userPermissionService = fixture.debugElement.injector.get(UserPermissionService);
|
userPermissionService = fixture.debugElement.injector.get(UserPermissionService);
|
||||||
let http: HttpClient;
|
let http: HttpClient;
|
||||||
@ -359,7 +391,7 @@ describe("ArtifactListTabComponent (inline template)", () => {
|
|||||||
comp.artifactList = mockArtifacts;
|
comp.artifactList = mockArtifacts;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
const el: HTMLAnchorElement = fixture.nativeElement.querySelector('a.max-width-100');
|
const el: HTMLAnchorElement = fixture.nativeElement.querySelector('.digest');
|
||||||
expect(el).toBeTruthy();
|
expect(el).toBeTruthy();
|
||||||
expect(el.textContent).toBeTruthy();
|
expect(el.textContent).toBeTruthy();
|
||||||
expect(el.textContent.trim()).toEqual("sha256:4875cda3");
|
expect(el.textContent.trim()).toEqual("sha256:4875cda3");
|
||||||
@ -372,7 +404,7 @@ describe("ArtifactListTabComponent (inline template)", () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const el: HTMLAnchorElement = fixture.nativeElement.querySelector('a.max-width-100');
|
const el: HTMLAnchorElement = fixture.nativeElement.querySelector('.digest');
|
||||||
expect(el).toBeTruthy();
|
expect(el).toBeTruthy();
|
||||||
expect(el.textContent).toBeTruthy();
|
expect(el.textContent).toBeTruthy();
|
||||||
expect(el.textContent.trim()).toEqual('sha256:3e33e3e3');
|
expect(el.textContent.trim()).toEqual('sha256:3e33e3e3');
|
||||||
@ -386,7 +418,7 @@ describe("ArtifactListTabComponent (inline template)", () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const el: HTMLAnchorElement = fixture.nativeElement.querySelector('a.max-width-100');
|
const el: HTMLAnchorElement = fixture.nativeElement.querySelector('.digest');
|
||||||
expect(el).toBeTruthy();
|
expect(el).toBeTruthy();
|
||||||
expect(el.textContent).toBeTruthy();
|
expect(el.textContent).toBeTruthy();
|
||||||
expect(el.textContent.trim()).toEqual('sha256:3e33e3e3');
|
expect(el.textContent.trim()).toEqual('sha256:3e33e3e3');
|
||||||
|
@ -11,14 +11,14 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild, } from "@angular/core";
|
import { Component, ElementRef, OnDestroy, OnInit, ViewChild, } from "@angular/core";
|
||||||
import { forkJoin, Observable, of, Subject, Subscription } from "rxjs";
|
import { forkJoin, Observable, of, Subject, Subscription } from "rxjs";
|
||||||
import { catchError, debounceTime, distinctUntilChanged, finalize, map } from 'rxjs/operators';
|
import { catchError, debounceTime, distinctUntilChanged, finalize, map } from 'rxjs/operators';
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
import { ClrDatagridComparatorInterface, ClrDatagridStateInterface, ClrLoadingState } from "@clr/angular";
|
import { ClrDatagridComparatorInterface, ClrDatagridStateInterface, ClrLoadingState } from "@clr/angular";
|
||||||
|
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { Comparator, ScanningResultService, UserPermissionService, USERSTATICPERMISSION, } from "../../../../../../../shared/services";
|
import { Comparator, } from "../../../../../../../shared/services";
|
||||||
import {
|
import {
|
||||||
calculatePage,
|
calculatePage,
|
||||||
clone,
|
clone,
|
||||||
@ -38,7 +38,14 @@ import { ArtifactService } from "../../../artifact.service";
|
|||||||
import { OperationService } from "../../../../../../../shared/components/operation/operation.service";
|
import { OperationService } from "../../../../../../../shared/components/operation/operation.service";
|
||||||
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../../../../../shared/entities/shared.const";
|
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../../../../../shared/entities/shared.const";
|
||||||
import { operateChanges, OperateInfo, OperationState } from "../../../../../../../shared/components/operation/operate";
|
import { operateChanges, OperateInfo, OperationState } from "../../../../../../../shared/components/operation/operate";
|
||||||
import { artifactDefault, ArtifactFront as Artifact, ArtifactFront, artifactPullCommands, mutipleFilter } from '../../../artifact';
|
import {
|
||||||
|
AccessoryType,
|
||||||
|
artifactDefault,
|
||||||
|
ArtifactFront as Artifact,
|
||||||
|
ArtifactFront,
|
||||||
|
artifactPullCommands,
|
||||||
|
mutipleFilter
|
||||||
|
} from '../../../artifact';
|
||||||
import { Project } from "../../../../../project";
|
import { Project } from "../../../../../project";
|
||||||
import { ArtifactService as NewArtifactService } from "../../../../../../../../../ng-swagger-gen/services/artifact.service";
|
import { ArtifactService as NewArtifactService } from "../../../../../../../../../ng-swagger-gen/services/artifact.service";
|
||||||
import { ADDITIONS } from "../../../artifact-additions/models";
|
import { ADDITIONS } from "../../../artifact-additions/models";
|
||||||
@ -50,16 +57,24 @@ import { ConfirmationMessage } from "../../../../../../global-confirmation-dialo
|
|||||||
import { ConfirmationAcknowledgement } from "../../../../../../global-confirmation-dialog/confirmation-state-message";
|
import { ConfirmationAcknowledgement } from "../../../../../../global-confirmation-dialog/confirmation-state-message";
|
||||||
import { UN_LOGGED_PARAM, YES } from "../../../../../../../account/sign-in/sign-in.service";
|
import { UN_LOGGED_PARAM, YES } from "../../../../../../../account/sign-in/sign-in.service";
|
||||||
import { Label } from "../../../../../../../../../ng-swagger-gen/models/label";
|
import { Label } from "../../../../../../../../../ng-swagger-gen/models/label";
|
||||||
import { LabelService } from "../../../../../../../../../ng-swagger-gen/services/label.service";
|
|
||||||
import { EventService, HarborEvent } from "../../../../../../../services/event-service/event.service";
|
import { EventService, HarborEvent } from "../../../../../../../services/event-service/event.service";
|
||||||
|
import { AppConfigService } from "src/app/services/app-config.service";
|
||||||
|
import { ArtifactListPageService } from "../../artifact-list-page.service";
|
||||||
|
import { ACCESSORY_PAGE_SIZE } from "./sub-accessories/sub-accessories.component";
|
||||||
|
import { Accessory } from "ng-swagger-gen/models/accessory";
|
||||||
|
|
||||||
export interface LabelState {
|
export interface LabelState {
|
||||||
iconsShow: boolean;
|
iconsShow: boolean;
|
||||||
label: Label;
|
label: Label;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AVAILABLE_TIME = '0001-01-01T00:00:00.000Z';
|
export const AVAILABLE_TIME = '0001-01-01T00:00:00.000Z';
|
||||||
const PAGE_SIZE: number = 100;
|
|
||||||
|
const CHECKING: string = 'checking';
|
||||||
|
const TRUE: string = 'true';
|
||||||
|
const FALSE: string = 'false';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'artifact-list-tab',
|
selector: 'artifact-list-tab',
|
||||||
templateUrl: './artifact-list-tab.component.html',
|
templateUrl: './artifact-list-tab.component.html',
|
||||||
@ -67,17 +82,10 @@ const PAGE_SIZE: number = 100;
|
|||||||
})
|
})
|
||||||
export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
signedCon: { [key: string]: any | string[] } = {};
|
projectId: number;
|
||||||
@Input() projectId: number;
|
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@Input() memberRoleID: number;
|
repoName: string;
|
||||||
@Input() repoName: string;
|
registryUrl: string;
|
||||||
@Input() isEmbedded: boolean;
|
|
||||||
@Input() hasSignedIn: boolean;
|
|
||||||
@Input() isGuest: boolean;
|
|
||||||
@Input() registryUrl: string;
|
|
||||||
@Input() withNotary: boolean;
|
|
||||||
@Input() withAdmiral: boolean;
|
|
||||||
artifactList: ArtifactFront[] = [];
|
artifactList: ArtifactFront[] = [];
|
||||||
availableTime = AVAILABLE_TIME;
|
availableTime = AVAILABLE_TIME;
|
||||||
showTagManifestOpened: boolean;
|
showTagManifestOpened: boolean;
|
||||||
@ -99,11 +107,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
loading = true;
|
loading = true;
|
||||||
copyFailed = false;
|
copyFailed = false;
|
||||||
selectedRow: Artifact[] = [];
|
selectedRow: Artifact[] = [];
|
||||||
|
|
||||||
imageLabels: LabelState[] = [];
|
|
||||||
imageStickLabels: LabelState[] = [];
|
|
||||||
imageFilterLabels: LabelState[] = [];
|
|
||||||
|
|
||||||
labelListOpen = false;
|
labelListOpen = false;
|
||||||
selectedTag: Artifact[];
|
selectedTag: Artifact[];
|
||||||
labelNameFilter: Subject<string> = new Subject<string>();
|
labelNameFilter: Subject<string> = new Subject<string>();
|
||||||
@ -134,12 +137,24 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
totalCount = 0;
|
totalCount = 0;
|
||||||
currentState: ClrDatagridStateInterface;
|
currentState: ClrDatagridStateInterface;
|
||||||
|
|
||||||
hasAddLabelImagePermission: boolean;
|
get hasAddLabelImagePermission(): boolean {
|
||||||
hasRetagImagePermission: boolean;
|
return this.artifactListPageService.hasAddLabelImagePermission();
|
||||||
hasDeleteImagePermission: boolean;
|
}
|
||||||
hasScanImagePermission: boolean;
|
get hasRetagImagePermission(): boolean {
|
||||||
hasEnabledScanner: boolean;
|
return this.artifactListPageService.hasRetagImagePermission();
|
||||||
scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
}
|
||||||
|
get hasDeleteImagePermission(): boolean {
|
||||||
|
return this.artifactListPageService.hasDeleteImagePermission();
|
||||||
|
}
|
||||||
|
get hasScanImagePermission(): boolean {
|
||||||
|
return this.artifactListPageService.hasScanImagePermission();
|
||||||
|
}
|
||||||
|
get hasEnabledScanner(): boolean {
|
||||||
|
return this.artifactListPageService.hasEnabledScanner();
|
||||||
|
}
|
||||||
|
get scanBtnState(): ClrLoadingState {
|
||||||
|
return this.artifactListPageService.getScanBtnState();
|
||||||
|
}
|
||||||
onSendingScanCommand: boolean;
|
onSendingScanCommand: boolean;
|
||||||
onSendingStopScanCommand: boolean = false;
|
onSendingStopScanCommand: boolean = false;
|
||||||
onStopScanArtifactsLength: number = 0;
|
onStopScanArtifactsLength: number = 0;
|
||||||
@ -161,23 +176,33 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
onScanArtifactsLength: number = 0;
|
onScanArtifactsLength: number = 0;
|
||||||
stopBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
stopBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
updateArtifactSub: Subscription;
|
updateArtifactSub: Subscription;
|
||||||
|
@ViewChild('tagName') nameSpan: ElementRef;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private errorHandlerService: ErrorHandler,
|
private errorHandlerService: ErrorHandler,
|
||||||
private userPermissionService: UserPermissionService,
|
|
||||||
private labelService: LabelService,
|
|
||||||
private artifactService: ArtifactService,
|
private artifactService: ArtifactService,
|
||||||
private newArtifactService: NewArtifactService,
|
private newArtifactService: NewArtifactService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private operationService: OperationService,
|
private operationService: OperationService,
|
||||||
private eventService: EventService,
|
private eventService: EventService,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private scanningService: ScanningResultService,
|
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private appConfigService: AppConfigService,
|
||||||
|
public artifactListPageService: ArtifactListPageService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.activatedRoute.params.subscribe(params => {
|
this.artifactListPageService.resetClonedLabels();
|
||||||
this.depth = this.activatedRoute.snapshot.params['depth'];
|
this.projectId = this.activatedRoute.snapshot?.parent?.parent?.params['id'];
|
||||||
|
let resolverData = this.activatedRoute.snapshot?.parent?.parent?.data;
|
||||||
|
if (resolverData) {
|
||||||
|
this.projectName = (<Project>resolverData['projectResolver']).name;
|
||||||
|
}
|
||||||
|
this.repoName = this.activatedRoute.snapshot?.parent?.params['repo'];
|
||||||
|
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
||||||
|
this.activatedRoute.params?.subscribe(params => {
|
||||||
|
this.depth = this.activatedRoute.snapshot?.firstChild?.params['depth'];
|
||||||
if (this.depth) {
|
if (this.depth) {
|
||||||
const arr: string[] = this.depth.split('-');
|
const arr: string[] = this.depth.split('-');
|
||||||
this.artifactDigest = this.depth.split('-')[arr.length - 1];
|
this.artifactDigest = this.depth.split('-')[arr.length - 1];
|
||||||
@ -202,6 +227,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.triggerSub) {
|
if (this.triggerSub) {
|
||||||
this.triggerSub.unsubscribe();
|
this.triggerSub.unsubscribe();
|
||||||
@ -220,6 +246,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.updateArtifactSub = null;
|
this.updateArtifactSub = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.hasInit = true;
|
this.hasInit = true;
|
||||||
this.depth = this.activatedRoute.snapshot.params['depth'];
|
this.depth = this.activatedRoute.snapshot.params['depth'];
|
||||||
@ -231,23 +258,15 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.errorHandlerService.error("Project ID cannot be unset.");
|
this.errorHandlerService.error("Project ID cannot be unset.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const resolverData = this.activatedRoute.snapshot.data;
|
const resolverData = this.activatedRoute.snapshot.params.data;
|
||||||
if (resolverData) {
|
if (resolverData) {
|
||||||
const pro: Project = <Project>resolverData['projectResolver'];
|
const pro: Project = <Project>resolverData['projectResolver'];
|
||||||
this.projectName = pro.name;
|
this.projectName = pro.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getProjectScanner();
|
|
||||||
if (!this.repoName) {
|
if (!this.repoName) {
|
||||||
this.errorHandlerService.error("Repo name cannot be unset.");
|
this.errorHandlerService.error("Repo name cannot be unset.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.triggerSub) {
|
|
||||||
this.triggerSub = this.artifactService.TriggerArtifactChan$.subscribe(res => {
|
|
||||||
let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
|
|
||||||
this.clrLoad(st);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.lastFilteredTagName = '';
|
this.lastFilteredTagName = '';
|
||||||
if (!this.labelNameFilterSub) {
|
if (!this.labelNameFilterSub) {
|
||||||
this.labelNameFilterSub = this.labelNameFilter
|
this.labelNameFilterSub = this.labelNameFilter
|
||||||
@ -256,7 +275,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
.subscribe((name: string) => {
|
.subscribe((name: string) => {
|
||||||
if (this.filterName.length) {
|
if (this.filterName.length) {
|
||||||
this.filterOnGoing = true;
|
this.filterOnGoing = true;
|
||||||
this.imageFilterLabels.forEach(data => {
|
this.artifactListPageService.imageFilterLabels.forEach(data => {
|
||||||
data.show = data.label.name.indexOf(this.filterName) !== -1;
|
data.show = data.label.name.indexOf(this.filterName) !== -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -269,26 +288,30 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
.subscribe((name: string) => {
|
.subscribe((name: string) => {
|
||||||
if (this.stickName.length) {
|
if (this.stickName.length) {
|
||||||
this.filterOnGoing = true;
|
this.filterOnGoing = true;
|
||||||
this.imageStickLabels.forEach(data => {
|
this.artifactListPageService.imageStickLabels.forEach(data => {
|
||||||
data.show = data.label.name.indexOf(this.stickName) !== -1;
|
data.show = data.label.name.indexOf(this.stickName) !== -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.getImagePermissionRule(this.projectId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get filterLabelPieceWidth() {
|
public get filterLabelPieceWidth() {
|
||||||
let len = this.lastFilteredTagName.length ? this.lastFilteredTagName.length * 6 + 60 : 115;
|
let len = this.lastFilteredTagName.length ? this.lastFilteredTagName.length * 6 + 60 : 115;
|
||||||
return len > 210 ? 210 : len;
|
return len > 210 ? 210 : len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get withNotary(): boolean {
|
||||||
|
return this.appConfigService.getConfig()?.with_notary;
|
||||||
|
}
|
||||||
|
|
||||||
doSearchArtifactByFilter(filterWords) {
|
doSearchArtifactByFilter(filterWords) {
|
||||||
this.lastFilteredTagName = filterWords;
|
this.lastFilteredTagName = filterWords;
|
||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
|
|
||||||
let st: ClrDatagridStateInterface = this.currentState;
|
let st: ClrDatagridStateInterface = this.currentState;
|
||||||
if (!st) {
|
if (!st) {
|
||||||
st = { page: {} };
|
st = {page: {}};
|
||||||
}
|
}
|
||||||
st.page.size = this.pageSize;
|
st.page.size = this.pageSize;
|
||||||
st.page.from = 0;
|
st.page.from = 0;
|
||||||
@ -299,12 +322,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.clrLoad(st);
|
this.clrLoad(st);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
clrDgRefresh(state: ClrDatagridStateInterface) {
|
clrDgRefresh(state: ClrDatagridStateInterface) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.clrLoad(state);
|
this.clrLoad(state);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clrLoad(state: ClrDatagridStateInterface): void {
|
clrLoad(state: ClrDatagridStateInterface): void {
|
||||||
this.artifactList = [];
|
this.artifactList = [];
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@ -316,7 +341,9 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
// Keep it for future filtering and sorting
|
// Keep it for future filtering and sorting
|
||||||
|
|
||||||
let pageNumber: number = calculatePage(state);
|
let pageNumber: number = calculatePage(state);
|
||||||
if (pageNumber <= 0) { pageNumber = 1; }
|
if (pageNumber <= 0) {
|
||||||
|
pageNumber = 1;
|
||||||
|
}
|
||||||
let sortBy: any = '';
|
let sortBy: any = '';
|
||||||
if (state.sort) {
|
if (state.sort) {
|
||||||
sortBy = state.sort.by as string | ClrDatagridComparatorInterface<any>;
|
sortBy = state.sort.by as string | ClrDatagridComparatorInterface<any>;
|
||||||
@ -350,7 +377,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
withLabel: true,
|
withLabel: true,
|
||||||
withScanOverview: true,
|
withScanOverview: true,
|
||||||
withTag: false,
|
withTag: false,
|
||||||
XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES
|
XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES,
|
||||||
|
withAccessory: false
|
||||||
};
|
};
|
||||||
this.newArtifactService.getArtifact(artifactParam).subscribe(
|
this.newArtifactService.getArtifact(artifactParam).subscribe(
|
||||||
res => {
|
res => {
|
||||||
@ -367,7 +395,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
withLabel: true,
|
withLabel: true,
|
||||||
withScanOverview: true,
|
withScanOverview: true,
|
||||||
withTag: false,
|
withTag: false,
|
||||||
XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES
|
XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES,
|
||||||
|
withAccessory: false
|
||||||
};
|
};
|
||||||
platFormAttr.push({platform: child.platform});
|
platFormAttr.push({platform: child.platform});
|
||||||
observableLists.push(this.newArtifactService.getArtifact(childParams));
|
observableLists.push(this.newArtifactService.getArtifact(childParams));
|
||||||
@ -383,6 +412,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
this.getPullCommand(this.artifactList);
|
this.getPullCommand(this.artifactList);
|
||||||
this.getArtifactTagsAsync(this.artifactList);
|
this.getArtifactTagsAsync(this.artifactList);
|
||||||
|
this.getAccessoriesAsync(this.artifactList);
|
||||||
|
this.checkCosignAsync(this.artifactList);
|
||||||
this.getIconsFromBackEnd();
|
this.getIconsFromBackEnd();
|
||||||
}, error => {
|
}, error => {
|
||||||
this.errorHandlerService.error(error);
|
this.errorHandlerService.error(error);
|
||||||
@ -399,7 +430,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
withScanOverview: true,
|
withScanOverview: true,
|
||||||
withTag: false,
|
withTag: false,
|
||||||
sort: getSortingString(state),
|
sort: getSortingString(state),
|
||||||
XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES
|
XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES,
|
||||||
|
withAccessory: false
|
||||||
};
|
};
|
||||||
Object.assign(listArtifactParams, params);
|
Object.assign(listArtifactParams, params);
|
||||||
this.newArtifactService.listArtifactsResponse(listArtifactParams)
|
this.newArtifactService.listArtifactsResponse(listArtifactParams)
|
||||||
@ -414,6 +446,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.artifactList = res.body;
|
this.artifactList = res.body;
|
||||||
this.getPullCommand(this.artifactList);
|
this.getPullCommand(this.artifactList);
|
||||||
this.getArtifactTagsAsync(this.artifactList);
|
this.getArtifactTagsAsync(this.artifactList);
|
||||||
|
this.getAccessoriesAsync(this.artifactList);
|
||||||
|
this.checkCosignAsync(this.artifactList);
|
||||||
this.getIconsFromBackEnd();
|
this.getIconsFromBackEnd();
|
||||||
}, error => {
|
}, error => {
|
||||||
// error
|
// error
|
||||||
@ -426,7 +460,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
let st: ClrDatagridStateInterface = this.currentState;
|
let st: ClrDatagridStateInterface = this.currentState;
|
||||||
if (!st) {
|
if (!st) {
|
||||||
st = { page: {} };
|
st = {page: {}};
|
||||||
st.page.size = this.pageSize;
|
st.page.size = this.pageSize;
|
||||||
st.page.from = 0;
|
st.page.from = 0;
|
||||||
st.page.to = this.pageSize - 1;
|
st.page.to = this.pageSize - 1;
|
||||||
@ -446,100 +480,18 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getAllLabels(): void {
|
|
||||||
// get all project labels
|
|
||||||
this.labelService.ListLabelsResponse({
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
page: 1,
|
|
||||||
scope: 'p',
|
|
||||||
projectId: this.projectId
|
|
||||||
}).subscribe(res => {
|
|
||||||
if (res.headers) {
|
|
||||||
const xHeader: string = res.headers.get("X-Total-Count");
|
|
||||||
const totalCount = parseInt(xHeader, 0);
|
|
||||||
let arr = res.body || [];
|
|
||||||
if (totalCount <= PAGE_SIZE) { // already gotten all project labels
|
|
||||||
if (arr && arr.length) {
|
|
||||||
arr.forEach(data => {
|
|
||||||
this.imageLabels.push({ 'iconsShow': false, 'label': data, 'show': true });
|
|
||||||
});
|
|
||||||
this.imageFilterLabels = clone(this.imageLabels);
|
|
||||||
this.imageStickLabels = clone(this.imageLabels);
|
|
||||||
}
|
|
||||||
} else { // get all the project labels in specified times
|
|
||||||
const times: number = Math.ceil(totalCount / PAGE_SIZE);
|
|
||||||
const observableList: Observable<Label[]>[] = [];
|
|
||||||
for (let i = 2; i <= times; i++) {
|
|
||||||
observableList.push(this.labelService.ListLabels({
|
|
||||||
page: i,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
scope: 'p',
|
|
||||||
projectId: this.projectId
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
this.handleLabelRes(observableList, arr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// get all global labels
|
|
||||||
this.labelService.ListLabelsResponse({
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
page: 1,
|
|
||||||
scope: 'g',
|
|
||||||
}).subscribe(res => {
|
|
||||||
if (res.headers) {
|
|
||||||
const xHeader: string = res.headers.get("X-Total-Count");
|
|
||||||
const totalCount = parseInt(xHeader, 0);
|
|
||||||
let arr = res.body || [];
|
|
||||||
if (totalCount <= PAGE_SIZE) { // already gotten all global labels
|
|
||||||
if (arr && arr.length) {
|
|
||||||
arr.forEach(data => {
|
|
||||||
this.imageLabels.push({ 'iconsShow': false, 'label': data, 'show': true });
|
|
||||||
});
|
|
||||||
this.imageFilterLabels = clone(this.imageLabels);
|
|
||||||
this.imageStickLabels = clone(this.imageLabels);
|
|
||||||
}
|
|
||||||
} else { // get all the global labels in specified times
|
|
||||||
const times: number = Math.ceil(totalCount / PAGE_SIZE);
|
|
||||||
const observableList: Observable<Label[]>[] = [];
|
|
||||||
for (let i = 2; i <= times; i++) {
|
|
||||||
observableList.push(this.labelService.ListLabels({
|
|
||||||
page: i,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
scope: 'g',
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
this.handleLabelRes(observableList, arr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
handleLabelRes(observableList: Observable<Label[]>[], arr: Label[]) {
|
|
||||||
forkJoin(observableList).subscribe(response => {
|
|
||||||
if (response && response.length) {
|
|
||||||
response.forEach(item => {
|
|
||||||
arr = arr.concat(item);
|
|
||||||
});
|
|
||||||
arr.forEach(data => {
|
|
||||||
this.imageLabels.push({ 'iconsShow': false, 'label': data, 'show': true });
|
|
||||||
});
|
|
||||||
this.imageFilterLabels = clone(this.imageLabels);
|
|
||||||
this.imageStickLabels = clone(this.imageLabels);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
labelSelectedChange(artifact?: Artifact[]): void {
|
labelSelectedChange(artifact?: Artifact[]): void {
|
||||||
this.imageStickLabels.forEach(data => {
|
this.artifactListPageService.imageStickLabels.forEach(data => {
|
||||||
data.iconsShow = false;
|
data.iconsShow = false;
|
||||||
data.show = true;
|
data.show = true;
|
||||||
});
|
});
|
||||||
if (artifact && artifact[0].labels && artifact[0].labels.length) {
|
if (artifact && artifact[0].labels && artifact[0].labels.length) {
|
||||||
artifact[0].labels.forEach((labelInfo: Label) => {
|
artifact[0].labels.forEach((labelInfo: Label) => {
|
||||||
let findedLabel = this.imageStickLabels.find(data => labelInfo.id === data['label'].id);
|
let findedLabel = this.artifactListPageService.imageStickLabels.find(data => labelInfo.id === data['label'].id);
|
||||||
if (findedLabel) {
|
if (findedLabel) {
|
||||||
this.imageStickLabels.splice(this.imageStickLabels.indexOf(findedLabel), 1);
|
this.artifactListPageService.imageStickLabels
|
||||||
this.imageStickLabels.unshift(findedLabel);
|
.splice(this.artifactListPageService.imageStickLabels.indexOf(findedLabel), 1);
|
||||||
|
this.artifactListPageService.imageStickLabels.unshift(findedLabel);
|
||||||
findedLabel.iconsShow = true;
|
findedLabel.iconsShow = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -552,12 +504,13 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.stickName = '';
|
this.stickName = '';
|
||||||
this.labelSelectedChange(this.selectedRow);
|
this.labelSelectedChange(this.selectedRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
canAddLabel(): boolean {
|
canAddLabel(): boolean {
|
||||||
if (this.selectedRow && this.selectedRow.length === 1) {
|
if (this.selectedRow && this.selectedRow.length === 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.selectedRow && this.selectedRow.length > 1) {
|
if (this.selectedRow && this.selectedRow.length > 1) {
|
||||||
for (let i = 0; i < this.selectedRow.length; i ++) {
|
for (let i = 0; i < this.selectedRow.length; i++) {
|
||||||
if (this.selectedRow[i].labels && this.selectedRow[i].labels.length) {
|
if (this.selectedRow[i].labels && this.selectedRow[i].labels.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -566,6 +519,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stickLabel(labelInfo: LabelState): void {
|
stickLabel(labelInfo: LabelState): void {
|
||||||
if (labelInfo && !labelInfo.iconsShow) {
|
if (labelInfo && !labelInfo.iconsShow) {
|
||||||
this.selectLabel(labelInfo);
|
this.selectLabel(labelInfo);
|
||||||
@ -593,16 +547,17 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
.subscribe(res => {
|
.subscribe(res => {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
// set the selected label in front
|
// set the selected label in front
|
||||||
this.imageStickLabels.splice(this.imageStickLabels.indexOf(labelInfo), 1);
|
this.artifactListPageService.imageStickLabels
|
||||||
this.imageStickLabels.some((data, i) => {
|
.splice(this.artifactListPageService.imageStickLabels.indexOf(labelInfo), 1);
|
||||||
|
this.artifactListPageService.imageStickLabels.some((data, i) => {
|
||||||
if (!data.iconsShow) {
|
if (!data.iconsShow) {
|
||||||
this.imageStickLabels.splice(i, 0, labelInfo);
|
this.artifactListPageService.imageStickLabels.splice(i, 0, labelInfo);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// when is the last one
|
// when is the last one
|
||||||
if (this.imageStickLabels.every(data => data.iconsShow === true)) {
|
if (this.artifactListPageService.imageStickLabels.every(data => data.iconsShow === true)) {
|
||||||
this.imageStickLabels.push(labelInfo);
|
this.artifactListPageService.imageStickLabels.push(labelInfo);
|
||||||
}
|
}
|
||||||
labelInfo.iconsShow = true;
|
labelInfo.iconsShow = true;
|
||||||
}, err => {
|
}, err => {
|
||||||
@ -627,7 +582,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.refresh();
|
this.refresh();
|
||||||
|
|
||||||
// insert the unselected label to groups with the same icons
|
// insert the unselected label to groups with the same icons
|
||||||
this.sortOperation(this.imageStickLabels, labelInfo);
|
this.sortOperation(this.artifactListPageService.imageStickLabels, labelInfo);
|
||||||
labelInfo.iconsShow = false;
|
labelInfo.iconsShow = false;
|
||||||
this.inprogress = false;
|
this.inprogress = false;
|
||||||
}, err => {
|
}, err => {
|
||||||
@ -651,7 +606,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
filterLabel(labelInfo: LabelState): void {
|
filterLabel(labelInfo: LabelState): void {
|
||||||
let labelId = labelInfo.label.id;
|
let labelId = labelInfo.label.id;
|
||||||
this.imageFilterLabels.filter(data => {
|
this.artifactListPageService.imageFilterLabels.filter(data => {
|
||||||
data.iconsShow = data.label.id === labelId;
|
data.iconsShow = data.label.id === labelId;
|
||||||
});
|
});
|
||||||
this.filterOneLabel = labelInfo.label;
|
this.filterOneLabel = labelInfo.label;
|
||||||
@ -660,7 +615,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
let st: ClrDatagridStateInterface = this.currentState;
|
let st: ClrDatagridStateInterface = this.currentState;
|
||||||
if (!st) {
|
if (!st) {
|
||||||
st = { page: {} };
|
st = {page: {}};
|
||||||
}
|
}
|
||||||
st.page.size = this.pageSize;
|
st.page.size = this.pageSize;
|
||||||
st.page.from = 0;
|
st.page.from = 0;
|
||||||
@ -678,7 +633,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
let st: ClrDatagridStateInterface = this.currentState;
|
let st: ClrDatagridStateInterface = this.currentState;
|
||||||
if (!st) {
|
if (!st) {
|
||||||
st = { page: {} };
|
st = {page: {}};
|
||||||
}
|
}
|
||||||
st.page.size = this.pageSize;
|
st.page.size = this.pageSize;
|
||||||
st.page.from = 0;
|
st.page.from = 0;
|
||||||
@ -691,20 +646,23 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
closeFilter(): void {
|
closeFilter(): void {
|
||||||
this.openLabelFilterPanel = false;
|
this.openLabelFilterPanel = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
reSortImageFilterLabels() {
|
reSortImageFilterLabels() {
|
||||||
if (this.imageFilterLabels && this.imageFilterLabels.length) {
|
if (this.artifactListPageService.imageFilterLabels && this.artifactListPageService.imageFilterLabels.length) {
|
||||||
for (let i = 0; i < this.imageFilterLabels.length; i++) {
|
for (let i = 0; i < this.artifactListPageService.imageFilterLabels.length; i++) {
|
||||||
if (this.imageFilterLabels[i].iconsShow) {
|
if (this.artifactListPageService.imageFilterLabels[i].iconsShow) {
|
||||||
const arr: LabelState[] = this.imageFilterLabels.splice(i, 1);
|
const arr: LabelState[] = this.artifactListPageService.imageFilterLabels.splice(i, 1);
|
||||||
this.imageFilterLabels.unshift(...arr);
|
this.artifactListPageService.imageFilterLabels.unshift(...arr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterPlaceholder(): string {
|
getFilterPlaceholder(): string {
|
||||||
return this.showlabel ? "" : 'ARTIFACT.FILTER_FOR_ARTIFACTS';
|
return this.showlabel ? "" : 'ARTIFACT.FILTER_FOR_ARTIFACTS';
|
||||||
}
|
}
|
||||||
|
|
||||||
openFlagEvent(isOpen: boolean): void {
|
openFlagEvent(isOpen: boolean): void {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
this.openLabelFilterPanel = true;
|
this.openLabelFilterPanel = true;
|
||||||
@ -714,7 +672,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.openSelectFilterPiece = true;
|
this.openSelectFilterPiece = true;
|
||||||
this.filterName = '';
|
this.filterName = '';
|
||||||
// redisplay all labels
|
// redisplay all labels
|
||||||
this.imageFilterLabels.forEach(data => {
|
this.artifactListPageService.imageFilterLabels.forEach(data => {
|
||||||
data.show = data.label.name.indexOf(this.filterName) !== -1;
|
data.show = data.label.name.indexOf(this.filterName) !== -1;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -729,7 +687,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
if (this.filterName.length) {
|
if (this.filterName.length) {
|
||||||
this.labelNameFilter.next(this.filterName);
|
this.labelNameFilter.next(this.filterName);
|
||||||
} else {
|
} else {
|
||||||
this.imageFilterLabels.every(data => data.show = true);
|
this.artifactListPageService.imageFilterLabels.every(data => data.show = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -737,7 +695,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
if (this.stickName.length) {
|
if (this.stickName.length) {
|
||||||
this.stickLabelNameFilter.next(this.stickName);
|
this.stickLabelNameFilter.next(this.stickName);
|
||||||
} else {
|
} else {
|
||||||
this.imageStickLabels.every(data => data.show = true);
|
this.artifactListPageService.imageStickLabels.every(data => data.show = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -758,6 +716,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeTransform(tagSize: string): string {
|
sizeTransform(tagSize: string): string {
|
||||||
return formatSize(tagSize);
|
return formatSize(tagSize);
|
||||||
}
|
}
|
||||||
@ -811,8 +770,40 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.confirmationDialog.open(message);
|
this.confirmationDialog.open(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteArtifactobservableLists: Observable<any>[] = [];
|
deleteArtifactobservableLists: Observable<any>[] = [];
|
||||||
|
|
||||||
confirmDeletion(message: ConfirmationAcknowledgement) {
|
confirmDeletion(message: ConfirmationAcknowledgement) {
|
||||||
|
if (message &&
|
||||||
|
message.source === ConfirmationTargets.ACCESSORY
|
||||||
|
&& message.state === ConfirmationState.CONFIRMED) { // delete one accessory
|
||||||
|
this.loading = true;
|
||||||
|
// init operation info
|
||||||
|
const opeMessage = new OperateInfo();
|
||||||
|
opeMessage.name = 'ACCESSORY.DELETE_ACCESSORY';
|
||||||
|
opeMessage.data.id = message.data.id;
|
||||||
|
opeMessage.state = OperationState.progressing;
|
||||||
|
opeMessage.data.name = message.data.digest.slice(0, 15);
|
||||||
|
this.operationService.publishInfo(opeMessage);
|
||||||
|
const params: NewArtifactService.DeleteArtifactParams = {
|
||||||
|
projectName: this.projectName,
|
||||||
|
repositoryName: this.repoName,
|
||||||
|
reference: message.data.digest,
|
||||||
|
};
|
||||||
|
this.newArtifactService.deleteArtifact(params)
|
||||||
|
.pipe(finalize(() => this.loading = false))
|
||||||
|
.subscribe(
|
||||||
|
res => {
|
||||||
|
this.errorHandlerService.info("ACCESSORY.DELETED_SUCCESS");
|
||||||
|
operateChanges(opeMessage, OperationState.success);
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.errorHandlerService.error(error);
|
||||||
|
operateChanges(opeMessage, OperationState.failure, errorHandler(error));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
if (message &&
|
if (message &&
|
||||||
message.source === ConfirmationTargets.TAG
|
message.source === ConfirmationTargets.TAG
|
||||||
&& message.state === ConfirmationState.CONFIRMED) {
|
&& message.state === ConfirmationState.CONFIRMED) {
|
||||||
@ -837,7 +828,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.selectedRow = [];
|
this.selectedRow = [];
|
||||||
if (deleteSuccessList.length === deleteResult.length) {
|
if (deleteSuccessList.length === deleteResult.length) {
|
||||||
// all is success
|
// all is success
|
||||||
let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
|
let st: ClrDatagridStateInterface = {page: {from: 0, to: this.pageSize - 1, size: this.pageSize}};
|
||||||
this.clrLoad(st);
|
this.clrLoad(st);
|
||||||
} else if (deleteErrorList.length === deleteResult.length) {
|
} else if (deleteErrorList.length === deleteResult.length) {
|
||||||
// all is error
|
// all is error
|
||||||
@ -847,7 +838,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
// some artifact delete success but it has error delete things
|
// some artifact delete success but it has error delete things
|
||||||
this.errorHandlerService.error(deleteErrorList[deleteErrorList.length - 1]);
|
this.errorHandlerService.error(deleteErrorList[deleteErrorList.length - 1]);
|
||||||
// if delete one success refresh list
|
// if delete one success refresh list
|
||||||
let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
|
let st: ClrDatagridStateInterface = {page: {from: 0, to: this.pageSize - 1, size: this.pageSize}};
|
||||||
this.clrLoad(st);
|
this.clrLoad(st);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -863,17 +854,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
operMessage.state = OperationState.progressing;
|
operMessage.state = OperationState.progressing;
|
||||||
operMessage.data.name = artifact.digest;
|
operMessage.data.name = artifact.digest;
|
||||||
this.operationService.publishInfo(operMessage);
|
this.operationService.publishInfo(operMessage);
|
||||||
// to do signature
|
|
||||||
// if (tag.signature) {
|
|
||||||
// forkJoin(this.translateService.get("BATCH.DELETED_FAILURE"),
|
|
||||||
// this.translateService.get("REPOSITORY.DELETION_SUMMARY_TAG_DENIED")).subscribe(res => {
|
|
||||||
// let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl +
|
|
||||||
// ":4443 -d ~/.docker/trust remove -p " +
|
|
||||||
// this.registryUrl + "/" + this.repoName +
|
|
||||||
// " " + name;
|
|
||||||
// operateChanges(operMessage, OperationState.failure, wrongInfo);
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
let params: NewArtifactService.DeleteArtifactParams = {
|
let params: NewArtifactService.DeleteArtifactParams = {
|
||||||
projectName: this.projectName,
|
projectName: this.projectName,
|
||||||
repositoryName: dbEncodeURIComponent(this.repoName),
|
repositoryName: dbEncodeURIComponent(this.repoName),
|
||||||
@ -909,9 +889,9 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
goIntoArtifactSummaryPage(artifact: Artifact): void {
|
goIntoArtifactSummaryPage(artifact: Artifact): void {
|
||||||
const relativeRouterLink: string[] = ['artifacts', artifact.digest];
|
const relativeRouterLink: string[] = ['artifacts', artifact.digest];
|
||||||
if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) {
|
if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) {
|
||||||
this.router.navigate(relativeRouterLink , { relativeTo: this.activatedRoute, queryParams: {[UN_LOGGED_PARAM]: YES} });
|
this.router.navigate(relativeRouterLink, {relativeTo: this.activatedRoute, queryParams: {[UN_LOGGED_PARAM]: YES}});
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(relativeRouterLink , { relativeTo: this.activatedRoute });
|
this.router.navigate(relativeRouterLink, {relativeTo: this.activatedRoute});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -940,10 +920,15 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
return VULNERABILITY_SCAN_STATUS.NOT_SCANNED;
|
return VULNERABILITY_SCAN_STATUS.NOT_SCANNED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if has running job, return false
|
// if has running job, return false
|
||||||
canScanNow(): boolean {
|
canScanNow(): boolean {
|
||||||
if (!this.hasScanImagePermission) { return false; }
|
if (!this.hasScanImagePermission) {
|
||||||
if (this.onSendingScanCommand) { return false; }
|
return false;
|
||||||
|
}
|
||||||
|
if (this.onSendingScanCommand) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (this.selectedRow && this.selectedRow.length) {
|
if (this.selectedRow && this.selectedRow.length) {
|
||||||
let flag: boolean = true;
|
let flag: boolean = true;
|
||||||
this.selectedRow.forEach(item => {
|
this.selectedRow.forEach(item => {
|
||||||
@ -956,26 +941,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
getImagePermissionRule(projectId: number): void {
|
|
||||||
const permissions = [
|
|
||||||
{ resource: USERSTATICPERMISSION.REPOSITORY_ARTIFACT_LABEL.KEY, action: USERSTATICPERMISSION.REPOSITORY_ARTIFACT_LABEL.VALUE.CREATE },
|
|
||||||
{ resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.PULL },
|
|
||||||
{ resource: USERSTATICPERMISSION.ARTIFACT.KEY, action: USERSTATICPERMISSION.ARTIFACT.VALUE.DELETE },
|
|
||||||
{ resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE },
|
|
||||||
];
|
|
||||||
this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array<boolean>) => {
|
|
||||||
this.hasAddLabelImagePermission = results[0];
|
|
||||||
this.hasRetagImagePermission = results[1];
|
|
||||||
this.hasDeleteImagePermission = results[2];
|
|
||||||
this.hasScanImagePermission = results[3];
|
|
||||||
// only has label permission
|
|
||||||
if (this.hasAddLabelImagePermission) {
|
|
||||||
if (!this.withAdmiral) {
|
|
||||||
this.getAllLabels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, error => this.errorHandlerService.error(error));
|
|
||||||
}
|
|
||||||
// Trigger scan
|
// Trigger scan
|
||||||
scanNow(): void {
|
scanNow(): void {
|
||||||
if (!this.selectedRow.length) {
|
if (!this.selectedRow.length) {
|
||||||
@ -989,15 +954,18 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.eventService.publish(HarborEvent.START_SCAN_ARTIFACT, this.repoName + "/" + digest);
|
this.eventService.publish(HarborEvent.START_SCAN_ARTIFACT, this.repoName + "/" + digest);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedRowHasVul(): boolean {
|
selectedRowHasVul(): boolean {
|
||||||
return !!(this.selectedRow
|
return !!(this.selectedRow
|
||||||
&& this.selectedRow[0]
|
&& this.selectedRow[0]
|
||||||
&& this.selectedRow[0].addition_links
|
&& this.selectedRow[0].addition_links
|
||||||
&& this.selectedRow[0].addition_links[ADDITIONS.VULNERABILITIES]);
|
&& this.selectedRow[0].addition_links[ADDITIONS.VULNERABILITIES]);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasVul(artifact: Artifact): boolean {
|
hasVul(artifact: Artifact): boolean {
|
||||||
return !!(artifact && artifact.addition_links && artifact.addition_links[ADDITIONS.VULNERABILITIES]);
|
return !!(artifact && artifact.addition_links && artifact.addition_links[ADDITIONS.VULNERABILITIES]);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitFinish(e: boolean) {
|
submitFinish(e: boolean) {
|
||||||
this.scanFinishedArtifactLength += 1;
|
this.scanFinishedArtifactLength += 1;
|
||||||
// all selected scan action has started
|
// all selected scan action has started
|
||||||
@ -1005,6 +973,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.onSendingScanCommand = e;
|
this.onSendingScanCommand = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
submitStopFinish(e: boolean) {
|
submitStopFinish(e: boolean) {
|
||||||
this.scanStoppedArtifactLength += 1;
|
this.scanStoppedArtifactLength += 1;
|
||||||
// all selected scan action has stopped
|
// all selected scan action has stopped
|
||||||
@ -1012,33 +981,18 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.onSendingScanCommand = e;
|
this.onSendingScanCommand = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pull command
|
// pull command
|
||||||
onCpError($event: any): void {
|
onCpError($event: any): void {
|
||||||
this.copyInput.setPullCommendShow();
|
this.copyInput.setPullCommendShow();
|
||||||
}
|
}
|
||||||
getProjectScanner(): void {
|
|
||||||
this.hasEnabledScanner = false;
|
|
||||||
this.scanBtnState = ClrLoadingState.LOADING;
|
|
||||||
this.scanningService.getProjectScanner(this.projectId)
|
|
||||||
.subscribe(response => {
|
|
||||||
if (response && "{}" !== JSON.stringify(response) && !response.disabled
|
|
||||||
&& response.health === "healthy") {
|
|
||||||
this.scanBtnState = ClrLoadingState.SUCCESS;
|
|
||||||
this.hasEnabledScanner = true;
|
|
||||||
} else {
|
|
||||||
this.scanBtnState = ClrLoadingState.ERROR;
|
|
||||||
}
|
|
||||||
}, error => {
|
|
||||||
this.scanBtnState = ClrLoadingState.ERROR;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleScanOverview(scanOverview: any): any {
|
handleScanOverview(scanOverview: any): any {
|
||||||
if (scanOverview) {
|
if (scanOverview) {
|
||||||
return Object.values(scanOverview)[0];
|
return Object.values(scanOverview)[0];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
goIntoIndexArtifact(artifact: Artifact) {
|
goIntoIndexArtifact(artifact: Artifact) {
|
||||||
let depth: string = '';
|
let depth: string = '';
|
||||||
if (this.depth) {
|
if (this.depth) {
|
||||||
@ -1046,13 +1000,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
depth = artifact.digest;
|
depth = artifact.digest;
|
||||||
}
|
}
|
||||||
const linkUrl = ['harbor', 'projects', this.projectId, 'repositories', this.repoName, 'depth', depth];
|
const linkUrl = ['harbor', 'projects', this.projectId, 'repositories', this.repoName, 'artifacts-tab', 'depth', depth];
|
||||||
if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) {
|
if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) {
|
||||||
this.router.navigate(linkUrl, {queryParams: {[UN_LOGGED_PARAM]: YES}});
|
this.router.navigate(linkUrl, {queryParams: {[UN_LOGGED_PARAM]: YES}});
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(linkUrl);
|
this.router.navigate(linkUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectFilterType() {
|
selectFilterType() {
|
||||||
this.lastFilteredTagName = '';
|
this.lastFilteredTagName = '';
|
||||||
if (this.filterByType === 'labels') {
|
if (this.filterByType === 'labels') {
|
||||||
@ -1064,14 +1019,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
this.openLabelFilterPiece = false;
|
this.openLabelFilterPiece = false;
|
||||||
this.filterOneLabel = this.initFilter;
|
this.filterOneLabel = this.initFilter;
|
||||||
this.showlabel = false;
|
this.showlabel = false;
|
||||||
this.imageFilterLabels.forEach(data => {
|
this.artifactListPageService.imageFilterLabels.forEach(data => {
|
||||||
data.iconsShow = false;
|
data.iconsShow = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
let st: ClrDatagridStateInterface = this.currentState;
|
let st: ClrDatagridStateInterface = this.currentState;
|
||||||
if (!st) {
|
if (!st) {
|
||||||
st = { page: {} };
|
st = {page: {}};
|
||||||
}
|
}
|
||||||
st.page.size = this.pageSize;
|
st.page.size = this.pageSize;
|
||||||
st.page.from = 0;
|
st.page.from = 0;
|
||||||
@ -1086,7 +1041,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
let st: ClrDatagridStateInterface = this.currentState;
|
let st: ClrDatagridStateInterface = this.currentState;
|
||||||
if (!st) {
|
if (!st) {
|
||||||
st = { page: {} };
|
st = {page: {}};
|
||||||
}
|
}
|
||||||
st.page.size = this.pageSize;
|
st.page.size = this.pageSize;
|
||||||
st.page.from = 0;
|
st.page.from = 0;
|
||||||
@ -1098,13 +1053,15 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.clrLoad(st);
|
this.clrLoad(st);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFilterReadonly() {
|
get isFilterReadonly() {
|
||||||
return this.filterByType === 'labels' ? 'readonly' : null;
|
return this.filterByType === 'labels' ? 'readonly' : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when finished, remove it from selectedRow
|
// when finished, remove it from selectedRow
|
||||||
scanFinished(artifact: Artifact) {
|
scanFinished(artifact: Artifact) {
|
||||||
if (this.selectedRow && this.selectedRow.length) {
|
if (this.selectedRow && this.selectedRow.length) {
|
||||||
for ( let i = 0; i < this.selectedRow.length; i++) {
|
for (let i = 0; i < this.selectedRow.length; i++) {
|
||||||
if (artifact.digest === this.selectedRow[i].digest) {
|
if (artifact.digest === this.selectedRow[i].digest) {
|
||||||
this.selectedRow.splice(i, 1);
|
this.selectedRow.splice(i, 1);
|
||||||
break;
|
break;
|
||||||
@ -1112,19 +1069,23 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getIconsFromBackEnd() {
|
getIconsFromBackEnd() {
|
||||||
if (this.artifactList && this.artifactList.length) {
|
if (this.artifactList && this.artifactList.length) {
|
||||||
this.artifactService.getIconsFromBackEnd(this.artifactList);
|
this.artifactService.getIconsFromBackEnd(this.artifactList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showDefaultIcon(event: any) {
|
showDefaultIcon(event: any) {
|
||||||
if (event && event.target) {
|
if (event && event.target) {
|
||||||
event.target.src = artifactDefault;
|
event.target.src = artifactDefault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getIcon(icon: string): SafeUrl {
|
getIcon(icon: string): SafeUrl {
|
||||||
return this.artifactService.getIcon(icon);
|
return this.artifactService.getIcon(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get Tags and display less than 9 tags(too many tags will make UI stuck)
|
// get Tags and display less than 9 tags(too many tags will make UI stuck)
|
||||||
getArtifactTagsAsync(artifacts: ArtifactFront[]) {
|
getArtifactTagsAsync(artifacts: ArtifactFront[]) {
|
||||||
if (artifacts && artifacts.length) {
|
if (artifacts && artifacts.length) {
|
||||||
@ -1152,9 +1113,63 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// get accessories
|
||||||
|
getAccessoriesAsync(artifacts: ArtifactFront[]) {
|
||||||
|
if (artifacts && artifacts.length) {
|
||||||
|
artifacts.forEach(item => {
|
||||||
|
const listTagParams: NewArtifactService.ListAccessoriesParams = {
|
||||||
|
projectName: this.projectName,
|
||||||
|
repositoryName: dbEncodeURIComponent(this.repoName),
|
||||||
|
reference: item.digest,
|
||||||
|
page: 1,
|
||||||
|
pageSize: ACCESSORY_PAGE_SIZE
|
||||||
|
};
|
||||||
|
this.newArtifactService.listAccessoriesResponse(listTagParams).subscribe(
|
||||||
|
res => {
|
||||||
|
if (res.headers) {
|
||||||
|
let xHeader: string = res.headers.get("x-total-count");
|
||||||
|
if (xHeader) {
|
||||||
|
item.accessoryNumber = Number.parseInt(xHeader, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.accessories = res.body;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkCosignAsync(artifacts: ArtifactFront[]) {
|
||||||
|
if (artifacts && artifacts.length) {
|
||||||
|
artifacts.forEach(item => {
|
||||||
|
item.coSigned = CHECKING;
|
||||||
|
const listTagParams: NewArtifactService.ListAccessoriesParams = {
|
||||||
|
projectName: this.projectName,
|
||||||
|
repositoryName: dbEncodeURIComponent(this.repoName),
|
||||||
|
reference: item.digest,
|
||||||
|
q: encodeURIComponent(`type=${AccessoryType.COSIGN}`),
|
||||||
|
page: 1,
|
||||||
|
pageSize: ACCESSORY_PAGE_SIZE
|
||||||
|
};
|
||||||
|
this.newArtifactService.listAccessories(listTagParams).subscribe(
|
||||||
|
res => {
|
||||||
|
if (res?.length) {
|
||||||
|
item.coSigned = TRUE;
|
||||||
|
} else {
|
||||||
|
item.coSigned = FALSE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
item.coSigned = FALSE;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
// return true if all selected rows are in "running" state
|
// return true if all selected rows are in "running" state
|
||||||
canStopScan(): boolean {
|
canStopScan(): boolean {
|
||||||
if (this.onSendingStopScanCommand) { return false; }
|
if (this.onSendingStopScanCommand) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (this.selectedRow && this.selectedRow.length) {
|
if (this.selectedRow && this.selectedRow.length) {
|
||||||
let flag: boolean = true;
|
let flag: boolean = true;
|
||||||
this.selectedRow.forEach(item => {
|
this.selectedRow.forEach(item => {
|
||||||
@ -1167,11 +1182,13 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
isRunningState(state: string): boolean {
|
isRunningState(state: string): boolean {
|
||||||
return state === VULNERABILITY_SCAN_STATUS.RUNNING ||
|
return state === VULNERABILITY_SCAN_STATUS.RUNNING ||
|
||||||
state === VULNERABILITY_SCAN_STATUS.PENDING ||
|
state === VULNERABILITY_SCAN_STATUS.PENDING ||
|
||||||
state === VULNERABILITY_SCAN_STATUS.SCHEDULED;
|
state === VULNERABILITY_SCAN_STATUS.SCHEDULED;
|
||||||
}
|
}
|
||||||
|
|
||||||
stopNow() {
|
stopNow() {
|
||||||
if (this.selectedRow && this.selectedRow.length) {
|
if (this.selectedRow && this.selectedRow.length) {
|
||||||
this.scanStoppedArtifactLength = 0;
|
this.scanStoppedArtifactLength = 0;
|
||||||
@ -1183,4 +1200,25 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isOverflow(): boolean {
|
||||||
|
if (!this.nameSpan) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !(this.nameSpan?.nativeElement?.clientWidth >= this.nameSpan?.nativeElement?.scrollWidth);
|
||||||
|
}
|
||||||
|
deleteAccessory(a: Accessory) {
|
||||||
|
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
|
||||||
|
titleKey = "ACCESSORY.DELETION_TITLE_ACCESSORY";
|
||||||
|
summaryKey = "ACCESSORY.DELETION_SUMMARY_ONE_ACCESSORY";
|
||||||
|
buttons = ConfirmationButtons.DELETE_CANCEL;
|
||||||
|
content = a.digest.slice(0, 15);
|
||||||
|
let message = new ConfirmationMessage(
|
||||||
|
titleKey,
|
||||||
|
summaryKey,
|
||||||
|
content,
|
||||||
|
a,
|
||||||
|
ConfirmationTargets.ACCESSORY,
|
||||||
|
buttons);
|
||||||
|
this.confirmationDialog.open(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
<clr-datagrid (clrDgRefresh)="clrLoad()" [clrDgLoading]="loading">
|
||||||
|
<clr-dg-column>{{'ACCESSORY.ACCESSORY' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>{{'PROJECT.TYPE' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>{{'ROBOT_ACCOUNT.CREATETION' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-row *ngFor="let a of displayedAccessories" [clrDgItem]="a">
|
||||||
|
<clr-dg-action-overflow>
|
||||||
|
<button class="action-item" (click)="delete(a)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||||
|
</clr-dg-action-overflow>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<div class="cell">
|
||||||
|
<div title="{{a?.digest}}" class="artifact-icon clr-display-inline-block">
|
||||||
|
<img *ngIf="getIcon(a.icon)" class="artifact-icon" [title]="a.type"
|
||||||
|
[src]="getIcon(a.icon)" (error)="showDefaultIcon($event)" />
|
||||||
|
</div>
|
||||||
|
<a href="javascript:void(0)" (click)="goIntoArtifactSummaryPage(a)" class="margin-left-5">{{a?.digest?.slice(0,15)}}</a>
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<div class="cell">
|
||||||
|
{{a.type}}
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<div class="cell">
|
||||||
|
{{size(a.size)}}
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<div class="cell">
|
||||||
|
{{a.creation_time | harborDatetime: 'short'}}
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
<clr-dg-footer *ngIf="total > pageSize">
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="currentPage" [clrDgTotalItems]="total">
|
||||||
|
<span *ngIf="total">{{pagination.firstItem + 1}}
|
||||||
|
-
|
||||||
|
{{pagination.lastItem +1 }} {{'ROBOT_ACCOUNT.OF' |
|
||||||
|
translate}} </span>
|
||||||
|
{{total}} {{'ROBOT_ACCOUNT.ITEMS' | translate}}
|
||||||
|
</clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
@ -0,0 +1,13 @@
|
|||||||
|
.artifact-icon {
|
||||||
|
width: 0.8rem;
|
||||||
|
height: 0.8rem;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.margin-left-5 {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { SharedTestingModule } from 'src/app/shared/shared.module';
|
||||||
|
import { SubAccessoriesComponent } from './sub-accessories.component';
|
||||||
|
import { Accessory } from "../../../../../../../../../../ng-swagger-gen/models/accessory";
|
||||||
|
import { AccessoryType } from "../../../../artifact";
|
||||||
|
import { ArtifactService as NewArtifactService } from "../../../../../../../../../../ng-swagger-gen/services/artifact.service";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
import { ArtifactDefaultService, ArtifactService } from '../../../../artifact.service';
|
||||||
|
|
||||||
|
describe('SubAccessoriesComponent', () => {
|
||||||
|
const mockedAccessories: Accessory[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
artifact_id: 1,
|
||||||
|
digest: 'sha256:test',
|
||||||
|
type: AccessoryType.COSIGN,
|
||||||
|
size: 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
artifact_id: 2,
|
||||||
|
digest: 'sha256:test2',
|
||||||
|
type: AccessoryType.COSIGN,
|
||||||
|
size: 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
artifact_id: 3,
|
||||||
|
digest: 'sha256:test3',
|
||||||
|
type: AccessoryType.COSIGN,
|
||||||
|
size: 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
artifact_id: 4,
|
||||||
|
digest: 'sha256:test4',
|
||||||
|
type: AccessoryType.COSIGN,
|
||||||
|
size: 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
artifact_id: 5,
|
||||||
|
digest: 'sha256:test5',
|
||||||
|
type: AccessoryType.COSIGN,
|
||||||
|
size: 1024
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page2: Accessory[] = [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
artifact_id: 6,
|
||||||
|
digest: 'sha256:test6',
|
||||||
|
type: AccessoryType.COSIGN,
|
||||||
|
size: 1024
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockedArtifactService = {
|
||||||
|
listAccessories() {
|
||||||
|
return of(page2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let component: SubAccessoriesComponent;
|
||||||
|
let fixture: ComponentFixture<SubAccessoriesComponent>;
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedTestingModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SubAccessoriesComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: NewArtifactService, useValue: mockedArtifactService },
|
||||||
|
{ provide: ArtifactService, useClass: ArtifactDefaultService },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SubAccessoriesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.accessories = mockedAccessories;
|
||||||
|
component.total = 6;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render rows', async () => {
|
||||||
|
await fixture.whenStable();
|
||||||
|
const rows = fixture.nativeElement.querySelectorAll('clr-dg-row');
|
||||||
|
expect(rows.length).toEqual(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render next page', async () => {
|
||||||
|
await fixture.whenStable();
|
||||||
|
const nextPageButton: HTMLButtonElement = fixture.nativeElement.querySelector('.pagination-next');
|
||||||
|
nextPageButton.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
const rows = fixture.nativeElement.querySelectorAll('clr-dg-row');
|
||||||
|
expect(rows.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,101 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
|
import { clone, dbEncodeURIComponent, formatSize } from "../../../../../../../../shared/units/utils";
|
||||||
|
import { UN_LOGGED_PARAM, YES } from "../../../../../../../../account/sign-in/sign-in.service";
|
||||||
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
import { Accessory } from "ng-swagger-gen/models/accessory";
|
||||||
|
import { ArtifactService as NewArtifactService } from "ng-swagger-gen/services/artifact.service";
|
||||||
|
import { ErrorHandler } from "../../../../../../../../shared/units/error-handler";
|
||||||
|
import { finalize } from "rxjs/operators";
|
||||||
|
import { SafeUrl } from '@angular/platform-browser';
|
||||||
|
import { ArtifactService } from "../../../../artifact.service";
|
||||||
|
import { artifactDefault } from '../../../../artifact';
|
||||||
|
|
||||||
|
export const ACCESSORY_PAGE_SIZE: number = 5;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'sub-accessories',
|
||||||
|
templateUrl: 'sub-accessories.component.html',
|
||||||
|
styleUrls: ['./sub-accessories.component.scss']
|
||||||
|
})
|
||||||
|
export class SubAccessoriesComponent implements OnInit {
|
||||||
|
@Input()
|
||||||
|
projectName: string;
|
||||||
|
@Input()
|
||||||
|
repositoryName: string;
|
||||||
|
@Input()
|
||||||
|
artifactDigest: string;
|
||||||
|
@Input()
|
||||||
|
accessories: Accessory[] = [];
|
||||||
|
@Output()
|
||||||
|
deleteAccessory: EventEmitter<Accessory> = new EventEmitter<Accessory>();
|
||||||
|
currentPage: number = 1;
|
||||||
|
@Input()
|
||||||
|
total: number = 0;
|
||||||
|
pageSize: number = ACCESSORY_PAGE_SIZE;
|
||||||
|
page: number = 1;
|
||||||
|
displayedAccessories: Accessory[] = [];
|
||||||
|
loading: boolean = false;
|
||||||
|
|
||||||
|
constructor(private activatedRoute: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private newArtifactService: NewArtifactService,
|
||||||
|
private artifactService: ArtifactService,
|
||||||
|
private errorHandlerService: ErrorHandler) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.displayedAccessories = clone(this.accessories);
|
||||||
|
}
|
||||||
|
|
||||||
|
size(size: number) {
|
||||||
|
return formatSize(size.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
getIcon(icon: string): SafeUrl {
|
||||||
|
return this.artifactService.getIcon(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
showDefaultIcon(event: any) {
|
||||||
|
if (event && event.target) {
|
||||||
|
event.target.src = artifactDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goIntoArtifactSummaryPage(accessory: Accessory): void {
|
||||||
|
const relativeRouterLink: string[] = ['artifacts', accessory.digest];
|
||||||
|
if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) {
|
||||||
|
this.router.navigate(relativeRouterLink, {relativeTo: this.activatedRoute, queryParams: {[UN_LOGGED_PARAM]: YES}});
|
||||||
|
} else {
|
||||||
|
this.router.navigate(relativeRouterLink, {relativeTo: this.activatedRoute});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(a: Accessory) {
|
||||||
|
this.deleteAccessory.emit(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
clrLoad() {
|
||||||
|
if (this.currentPage === 1) {
|
||||||
|
this.displayedAccessories = clone(this.accessories);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
const listTagParams: NewArtifactService.ListAccessoriesParams = {
|
||||||
|
projectName: this.projectName,
|
||||||
|
repositoryName: dbEncodeURIComponent(this.repositoryName),
|
||||||
|
reference: this.artifactDigest,
|
||||||
|
page: 1,
|
||||||
|
pageSize: ACCESSORY_PAGE_SIZE
|
||||||
|
};
|
||||||
|
this.newArtifactService.listAccessories(listTagParams)
|
||||||
|
.pipe(finalize(() => this.loading = false))
|
||||||
|
.subscribe(
|
||||||
|
res => {
|
||||||
|
this.displayedAccessories = res;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.errorHandlerService.error(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
<section class="overview-section">
|
|
||||||
<div class="title-wrapper">
|
|
||||||
<div class="title-block arrow-block" *ngIf="withAdmiral">
|
|
||||||
<clr-icon class="rotate-90 arrow-back" shape="arrow" size="36" (click)="goBack()"></clr-icon>
|
|
||||||
</div>
|
|
||||||
<div class="title-block">
|
|
||||||
<h2 sub-header-title class="custom-h2" *ngIf="!artifactDigest">{{showCurrentTitle}}</h2>
|
|
||||||
<h2 sub-header-title class="custom-h2" *ngIf="artifactDigest">{{artifactDigest | slice:0:15}}</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="detail-section">
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
|
||||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
|
||||||
<ul id="configTabs" class="nav" role="tablist">
|
|
||||||
<li role="presentation" class="nav-item" *ngIf="!artifactDigest">
|
|
||||||
<button id="repo-info" class="btn btn-link nav-link" aria-controls="info" [class.active]='isCurrentTabLink("repo-info")'
|
|
||||||
type="button" (click)='tabLinkClick("repo-info")'>{{'REPOSITORY.INFO' | translate}}</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button id="repo-image" class="btn btn-link nav-link active" aria-controls="image" [class.active]='isCurrentTabLink("repo-image")'
|
|
||||||
type="button" (click)='tabLinkClick("repo-image")'>{{'REPOSITORY.ARTIFACTS' | translate}}</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<section id="info" role="tabpanel" aria-labelledby="repo-info" [hidden]='!isCurrentTabContent("info")'>
|
|
||||||
<form #repoInfoForm="ngForm">
|
|
||||||
<div id="info-edit-button">
|
|
||||||
<button class="btn " [disabled]="editing || !hasProjectAdminRole " (click)="editInfo()">
|
|
||||||
<clr-icon shape="pencil" size="16"></clr-icon> {{'BUTTON.EDIT' | translate}}
|
|
||||||
</button>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 1024" preserveAspectRatio="xMinYMin" class="markdown">
|
|
||||||
<path d="M950.154 192H73.846C33.127 192 0 225.12699999999995 0 265.846v492.308C0 798.875 33.127 832 73.846 832h876.308c40.721 0 73.846-33.125 73.846-73.846V265.846C1024 225.12699999999995 990.875 192 950.154 192zM576 703.875L448 704V512l-96 123.077L256 512v192H128V320h128l96 128 96-128 128-0.125V703.875zM767.091 735.875L608 512h96V320h128v192h96L767.091 735.875z" />
|
|
||||||
</svg>
|
|
||||||
<span>{{ 'REPOSITORY.MARKDOWN' | translate }}</span>
|
|
||||||
</div>
|
|
||||||
<div id="no-editing" *ngIf="!editing">
|
|
||||||
<div *ngIf="!hasInfo()" class="no-info-div">
|
|
||||||
<p>{{'REPOSITORY.NO_INFO' | translate }}<p>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="hasInfo()" class="info-div">
|
|
||||||
<div class="info-pre" [innerHTML]="imageInfo | markdown"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="editing">
|
|
||||||
<textarea id="info-edit-textarea" class="clr-textarea w-100" rows="5" name="info-edit-textarea"
|
|
||||||
[(ngModel)]="imageInfo"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="" *ngIf="editing">
|
|
||||||
<button id="edit-save" class="btn btn-primary" [disabled]="!hasChanges()" (click)="saveInfo()">{{'BUTTON.SAVE' | translate}}</button>
|
|
||||||
<button id="edit-cancel" class="btn" (click)="cancelInfo()">{{'BUTTON.CANCEL' | translate}}</button>
|
|
||||||
</div>
|
|
||||||
<confirmation-dialog #confirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
<section id="image" role="tabpanel" aria-labelledby="repo-image" [hidden]='!isCurrentTabContent("image")'>
|
|
||||||
<div id="images-container">
|
|
||||||
<artifact-list-tab ngProjectAs="clr-dg-row-detail"
|
|
||||||
class="sub-grid-custom" [repoName]="repoName" artifact [registryUrl]="registryUrl" [withNotary]="withNotary" [withAdmiral]="withAdmiral" [hasSignedIn]="hasSignedIn"
|
|
||||||
[isGuest]="isGuest" [projectId]="projectId" [memberRoleID]="memberRoleID"></artifact-list-tab>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
@ -1,101 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed, waitForAsync, } from '@angular/core/testing';
|
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
|
||||||
import { ArtifactListComponent } from './artifact-list.component';
|
|
||||||
import { of } from "rxjs";
|
|
||||||
import { delay } from 'rxjs/operators';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { SystemInfo, SystemInfoDefaultService, SystemInfoService, } from "../../../../../../shared/services";
|
|
||||||
import { ArtifactDefaultService, ArtifactService } from "../../artifact.service";
|
|
||||||
import { ErrorHandler } from "../../../../../../shared/units/error-handler";
|
|
||||||
import { RepositoryService as NewRepositoryService } from "../../../../../../../../ng-swagger-gen/services/repository.service";
|
|
||||||
import { SharedTestingModule } from "../../../../../../shared/shared.module";
|
|
||||||
|
|
||||||
describe('ArtifactListComponent (inline template)', () => {
|
|
||||||
|
|
||||||
let compRepo: ArtifactListComponent;
|
|
||||||
let fixture: ComponentFixture<ArtifactListComponent>;
|
|
||||||
let systemInfoService: SystemInfoService;
|
|
||||||
let artifactService: ArtifactService;
|
|
||||||
let spyRepos: jasmine.Spy;
|
|
||||||
let spySystemInfo: jasmine.Spy;
|
|
||||||
let mockActivatedRoute = {
|
|
||||||
data: of(
|
|
||||||
{
|
|
||||||
projectResolver: {
|
|
||||||
name: 'library'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
params: {
|
|
||||||
subscribe: () => {
|
|
||||||
return of(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
snapshot: { data: null }
|
|
||||||
};
|
|
||||||
let mockSystemInfo: SystemInfo = {
|
|
||||||
'with_notary': true,
|
|
||||||
'with_admiral': false,
|
|
||||||
'admiral_endpoint': 'NA',
|
|
||||||
'auth_mode': 'db_auth',
|
|
||||||
'registry_url': '10.112.122.56',
|
|
||||||
'project_creation_restriction': 'everyone',
|
|
||||||
'self_registration': true,
|
|
||||||
'has_ca_root': false,
|
|
||||||
'harbor_version': 'v1.1.1-rc1-160-g565110d'
|
|
||||||
};
|
|
||||||
|
|
||||||
let newRepositoryService = {
|
|
||||||
updateRepository: () => of(null),
|
|
||||||
getRepository: () => of({description: ''})
|
|
||||||
};
|
|
||||||
|
|
||||||
const fakedErrorHandler = {
|
|
||||||
error: () => {}
|
|
||||||
};
|
|
||||||
beforeEach(waitForAsync(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
SharedTestingModule,
|
|
||||||
],
|
|
||||||
schemas: [
|
|
||||||
NO_ERRORS_SCHEMA
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
ArtifactListComponent
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: ErrorHandler, useValue: fakedErrorHandler },
|
|
||||||
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
|
|
||||||
{ provide: ArtifactService, useClass: ArtifactDefaultService },
|
|
||||||
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
|
||||||
{ provide: NewRepositoryService, useValue: newRepositoryService},
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(ArtifactListComponent);
|
|
||||||
compRepo = fixture.componentInstance;
|
|
||||||
compRepo.projectId = 1;
|
|
||||||
compRepo.hasProjectAdminRole = true;
|
|
||||||
compRepo.repoName = 'library/nginx';
|
|
||||||
systemInfoService = fixture.debugElement.injector.get(SystemInfoService);
|
|
||||||
artifactService = fixture.debugElement.injector.get(ArtifactService);
|
|
||||||
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(of(mockSystemInfo).pipe(delay(0)));
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
let originalTimeout;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
|
|
||||||
});
|
|
||||||
it('should create', () => {
|
|
||||||
expect(compRepo).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,205 +0,0 @@
|
|||||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { ArtifactClickEvent, State, SystemInfo, SystemInfoService } from "../../../../../../shared/services";
|
|
||||||
import { ConfirmationDialogComponent, } from "../../../../../../shared/components/confirmation-dialog";
|
|
||||||
import { ErrorHandler } from "../../../../../../shared/units/error-handler";
|
|
||||||
import { ArtifactService } from "../../artifact.service";
|
|
||||||
import { ConfirmationState, ConfirmationTargets } from "../../../../../../shared/entities/shared.const";
|
|
||||||
import { ActivatedRoute } from "@angular/router";
|
|
||||||
import { Project } from '../../../../project';
|
|
||||||
import { RepositoryService as NewRepositoryService } from "../../../../../../../../ng-swagger-gen/services/repository.service";
|
|
||||||
import { dbEncodeURIComponent } from '../../../../../../shared/units/utils';
|
|
||||||
import { ConfirmationMessage } from "../../../../../global-confirmation-dialog/confirmation-message";
|
|
||||||
import { ConfirmationAcknowledgement } from "../../../../../global-confirmation-dialog/confirmation-state-message";
|
|
||||||
|
|
||||||
const TabLinkContentMap: { [index: string]: string } = {
|
|
||||||
'repo-info': 'info',
|
|
||||||
'repo-image': 'image'
|
|
||||||
};
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'artifact-list',
|
|
||||||
templateUrl: './artifact-list.component.html',
|
|
||||||
styleUrls: ['./artifact-list.component.scss']
|
|
||||||
})
|
|
||||||
export class ArtifactListComponent implements OnInit {
|
|
||||||
signedCon: { [key: string]: any | string[] } = {};
|
|
||||||
@Input() projectId: number;
|
|
||||||
@Input() memberRoleID: number;
|
|
||||||
@Input() repoName: string;
|
|
||||||
@Input() hasSignedIn: boolean;
|
|
||||||
@Input() hasProjectAdminRole: boolean;
|
|
||||||
@Input() isGuest: boolean;
|
|
||||||
@Output() tagClickEvent = new EventEmitter<ArtifactClickEvent>();
|
|
||||||
@Output() backEvt: EventEmitter<any> = new EventEmitter<any>();
|
|
||||||
@Output() putArtifactReferenceArr: EventEmitter<string[]> = new EventEmitter<[]>();
|
|
||||||
|
|
||||||
onGoing = false;
|
|
||||||
editing = false;
|
|
||||||
inProgress = true;
|
|
||||||
currentTabID = 'repo-image';
|
|
||||||
systemInfo: SystemInfo;
|
|
||||||
|
|
||||||
imageInfo: string;
|
|
||||||
orgImageInfo: string;
|
|
||||||
|
|
||||||
timerHandler: any;
|
|
||||||
projectName: string = '';
|
|
||||||
@ViewChild('confirmationDialog')
|
|
||||||
confirmationDlg: ConfirmationDialogComponent;
|
|
||||||
showCurrentTitle: string;
|
|
||||||
artifactDigest: string;
|
|
||||||
constructor(
|
|
||||||
private errorHandler: ErrorHandler,
|
|
||||||
private systemInfoService: SystemInfoService,
|
|
||||||
private artifactService: ArtifactService,
|
|
||||||
private newRepositoryService: NewRepositoryService,
|
|
||||||
private translate: TranslateService,
|
|
||||||
private activatedRoute: ActivatedRoute,
|
|
||||||
) {
|
|
||||||
this.activatedRoute.params.subscribe(params => {
|
|
||||||
let depth = this.activatedRoute.snapshot.params['depth'];
|
|
||||||
if (depth) {
|
|
||||||
const arr: string[] = depth.split('-');
|
|
||||||
this.artifactDigest = depth.split('-')[arr.length - 1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public get registryUrl(): string {
|
|
||||||
return this.systemInfo ? this.systemInfo.registry_url : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public get withNotary(): boolean {
|
|
||||||
return this.systemInfo ? this.systemInfo.with_notary : false;
|
|
||||||
}
|
|
||||||
public get withAdmiral(): boolean {
|
|
||||||
return this.systemInfo ? this.systemInfo.with_admiral : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
if (!this.projectId) {
|
|
||||||
this.errorHandler.error('Project ID cannot be unset.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const resolverData = this.activatedRoute.snapshot.data;
|
|
||||||
if (resolverData) {
|
|
||||||
const pro: Project = <Project>resolverData['projectResolver'];
|
|
||||||
this.projectName = pro.name;
|
|
||||||
}
|
|
||||||
this.showCurrentTitle = this.repoName || 'null';
|
|
||||||
this.retrieve();
|
|
||||||
this.inProgress = false;
|
|
||||||
this.artifactService.TriggerArtifactChan$.subscribe(res => {
|
|
||||||
if (res === 'repoName') {
|
|
||||||
this.showCurrentTitle = this.repoName;
|
|
||||||
} else {
|
|
||||||
this.showCurrentTitle = res[res.length - 1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieve(state?: State) {
|
|
||||||
let params: NewRepositoryService.GetRepositoryParams = {
|
|
||||||
projectName: this.projectName,
|
|
||||||
repositoryName: dbEncodeURIComponent(this.repoName),
|
|
||||||
};
|
|
||||||
this.newRepositoryService.getRepository(params)
|
|
||||||
.subscribe(response => {
|
|
||||||
this.orgImageInfo = response.description;
|
|
||||||
this.imageInfo = response.description;
|
|
||||||
}, error => this.errorHandler.error(error));
|
|
||||||
this.systemInfoService.getSystemInfo()
|
|
||||||
.subscribe(systemInfo => this.systemInfo = systemInfo, error => this.errorHandler.error(error));
|
|
||||||
}
|
|
||||||
refresh() {
|
|
||||||
this.retrieve();
|
|
||||||
}
|
|
||||||
isCurrentTabLink(tabID: string): boolean {
|
|
||||||
return this.currentTabID === tabID;
|
|
||||||
}
|
|
||||||
|
|
||||||
isCurrentTabContent(ContentID: string): boolean {
|
|
||||||
return TabLinkContentMap[this.currentTabID] === ContentID;
|
|
||||||
}
|
|
||||||
|
|
||||||
tabLinkClick(tabID: string) {
|
|
||||||
this.currentTabID = tabID;
|
|
||||||
}
|
|
||||||
|
|
||||||
goBack(): void {
|
|
||||||
this.backEvt.emit(this.projectId);
|
|
||||||
}
|
|
||||||
|
|
||||||
hasChanges() {
|
|
||||||
return this.imageInfo !== this.orgImageInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(): void {
|
|
||||||
this.imageInfo = this.orgImageInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasInfo() {
|
|
||||||
return this.imageInfo && this.imageInfo.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
editInfo() {
|
|
||||||
this.editing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveInfo() {
|
|
||||||
if (!this.hasChanges()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.onGoing = true;
|
|
||||||
let params: NewRepositoryService.UpdateRepositoryParams = {
|
|
||||||
repositoryName: dbEncodeURIComponent(this.repoName),
|
|
||||||
repository: {description: this.imageInfo},
|
|
||||||
projectName: this.projectName,
|
|
||||||
};
|
|
||||||
this.newRepositoryService.updateRepository(params)
|
|
||||||
.subscribe(() => {
|
|
||||||
this.onGoing = false;
|
|
||||||
this.translate.get('CONFIG.SAVE_SUCCESS').subscribe((res: string) => {
|
|
||||||
this.errorHandler.info(res);
|
|
||||||
});
|
|
||||||
this.editing = false;
|
|
||||||
this.refresh();
|
|
||||||
}, error => {
|
|
||||||
this.onGoing = false;
|
|
||||||
this.errorHandler.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelInfo() {
|
|
||||||
let msg = new ConfirmationMessage(
|
|
||||||
'CONFIG.CONFIRM_TITLE',
|
|
||||||
'CONFIG.CONFIRM_SUMMARY',
|
|
||||||
'',
|
|
||||||
{},
|
|
||||||
ConfirmationTargets.CONFIG
|
|
||||||
);
|
|
||||||
this.confirmationDlg.open(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmCancel(ack: ConfirmationAcknowledgement): void {
|
|
||||||
this.editing = false;
|
|
||||||
if (ack && ack.source === ConfirmationTargets.CONFIG &&
|
|
||||||
ack.state === ConfirmationState.CONFIRMED) {
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
<div class="arrow-block" *ngIf="!withAdmiral">
|
<div class="arrow-block">
|
||||||
<span class="back-icon margin-right-5px"><</span>
|
<span class="back-icon margin-right-5px"><</span>
|
||||||
<a class="pl-0" (click)="goBackPro()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
|
<a class="pl-0" (click)="goBackPro()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
|
||||||
<span class="back-icon"><</span>
|
<span class="back-icon"><</span>
|
||||||
@ -11,9 +11,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="title-wrapper">
|
<div class="title-wrapper">
|
||||||
<div class="title-block arrow-block" *ngIf="withAdmiral">
|
|
||||||
<clr-icon class="rotate-90 arrow-back" shape="arrow" size="36" (click)="onBack()"></clr-icon>
|
|
||||||
</div>
|
|
||||||
<div class="title-block">
|
<div class="title-block">
|
||||||
<h2 class="custom-h2 center-align-items">
|
<h2 class="custom-h2 center-align-items">
|
||||||
<div class="artifact-icon clr-display-inline-block" *ngIf="artifact.icon">
|
<div class="artifact-icon clr-display-inline-block" *ngIf="artifact.icon">
|
||||||
|
@ -43,10 +43,6 @@ export class ArtifactSummaryComponent implements OnInit {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
get withAdmiral(): boolean {
|
|
||||||
return this.appConfigService.getConfig().with_admiral;
|
|
||||||
}
|
|
||||||
|
|
||||||
goBack(): void {
|
goBack(): void {
|
||||||
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName]);
|
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName]);
|
||||||
}
|
}
|
||||||
@ -61,7 +57,7 @@ export class ArtifactSummaryComponent implements OnInit {
|
|||||||
jumpDigest(index: number) {
|
jumpDigest(index: number) {
|
||||||
const arr: string[] = this.referArtifactNameArray.slice(0, index + 1 );
|
const arr: string[] = this.referArtifactNameArray.slice(0, index + 1 );
|
||||||
if ( arr && arr.length) {
|
if ( arr && arr.length) {
|
||||||
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName, "depth", arr.join('-')]);
|
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName, "artifacts-tab", "depth", arr.join('-')]);
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName]);
|
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName]);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
</clr-dg-action-bar>
|
</clr-dg-action-bar>
|
||||||
<clr-dg-column [clrDgField]="'name'">{{'TAG.NAME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgField]="'name'">{{'TAG.NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column *ngIf="hasPullCommand()">{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
<clr-dg-column *ngIf="hasPullCommand()">{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
<clr-dg-column *ngIf="withNotary">{{'ACCESSORY.NOTARY_SIGNED' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="'pull_time'">{{'TAG.PULL_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="'pull_time'">{{'TAG.PULL_TIME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="'push_time'">{{'TAG.PUSH_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="'push_time'">{{'TAG.PUSH_TIME' | translate}}</clr-dg-column>
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
import { SharedModule } from "../../../../shared/shared.module";
|
import { SharedModule } from "../../../../shared/shared.module";
|
||||||
import { ArtifactListPageComponent } from "./artifact-list-page/artifact-list-page.component";
|
import { ArtifactListPageComponent } from "./artifact-list-page/artifact-list-page.component";
|
||||||
import { ArtifactListComponent } from "./artifact-list-page/artifact-list/artifact-list.component";
|
|
||||||
import { ArtifactListTabComponent } from "./artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component";
|
import { ArtifactListTabComponent } from "./artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component";
|
||||||
import { ArtifactSummaryComponent } from "./artifact-summary.component";
|
import { ArtifactSummaryComponent } from "./artifact-summary.component";
|
||||||
import { ArtifactTagComponent } from "./artifact-tag/artifact-tag.component";
|
import { ArtifactTagComponent } from "./artifact-tag/artifact-tag.component";
|
||||||
@ -19,25 +18,45 @@ import { ResultTipComponent } from "./vulnerability-scanning/result-tip.componen
|
|||||||
import { ResultBarChartComponent } from "./vulnerability-scanning/result-bar-chart.component";
|
import { ResultBarChartComponent } from "./vulnerability-scanning/result-bar-chart.component";
|
||||||
import { ResultTipHistogramComponent } from "./vulnerability-scanning/result-tip-histogram/result-tip-histogram.component";
|
import { ResultTipHistogramComponent } from "./vulnerability-scanning/result-tip-histogram/result-tip-histogram.component";
|
||||||
import { HistogramChartComponent } from "./vulnerability-scanning/histogram-chart/histogram-chart.component";
|
import { HistogramChartComponent } from "./vulnerability-scanning/histogram-chart/histogram-chart.component";
|
||||||
|
import { ArtifactInfoComponent } from "./artifact-list-page/artifact-list/artifact-info/artifact-info.component";
|
||||||
|
import { SubAccessoriesComponent } from "./artifact-list-page/artifact-list/artifact-list-tab/sub-accessories/sub-accessories.component";
|
||||||
|
import { ArtifactListPageService } from "./artifact-list-page/artifact-list-page.service";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: ':repo',
|
path: ':repo',
|
||||||
component: ArtifactListPageComponent,
|
component: ArtifactListPageComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'info-tab',
|
||||||
|
component: ArtifactInfoComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':repo/depth/:depth',
|
path: 'artifacts-tab',
|
||||||
|
component: ArtifactListTabComponent
|
||||||
|
},
|
||||||
|
{ path: '', redirectTo: 'artifacts-tab', pathMatch: 'full' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':repo',
|
||||||
component: ArtifactListPageComponent,
|
component: ArtifactListPageComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'artifacts-tab/depth/:depth',
|
||||||
|
component: ArtifactListTabComponent
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':repo/artifacts/:digest',
|
path: ':repo/artifacts-tab/artifacts/:digest',
|
||||||
component: ArtifactSummaryComponent,
|
component: ArtifactSummaryComponent,
|
||||||
resolve: {
|
resolve: {
|
||||||
artifactResolver: ArtifactDetailRoutingResolverService
|
artifactResolver: ArtifactDetailRoutingResolverService
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':repo/depth/:depth/artifacts/:digest',
|
path: ':repo/artifacts-tab/depth/:depth/artifacts/:digest',
|
||||||
component: ArtifactSummaryComponent,
|
component: ArtifactSummaryComponent,
|
||||||
resolve: {
|
resolve: {
|
||||||
artifactResolver: ArtifactDetailRoutingResolverService
|
artifactResolver: ArtifactDetailRoutingResolverService
|
||||||
@ -47,7 +66,6 @@ const routes: Routes = [
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ArtifactListPageComponent,
|
ArtifactListPageComponent,
|
||||||
ArtifactListComponent,
|
|
||||||
ArtifactListTabComponent,
|
ArtifactListTabComponent,
|
||||||
ArtifactSummaryComponent,
|
ArtifactSummaryComponent,
|
||||||
ArtifactTagComponent,
|
ArtifactTagComponent,
|
||||||
@ -61,14 +79,17 @@ const routes: Routes = [
|
|||||||
ResultTipComponent,
|
ResultTipComponent,
|
||||||
ResultBarChartComponent,
|
ResultBarChartComponent,
|
||||||
ResultTipHistogramComponent,
|
ResultTipHistogramComponent,
|
||||||
HistogramChartComponent
|
HistogramChartComponent,
|
||||||
|
ArtifactInfoComponent,
|
||||||
|
SubAccessoriesComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: ArtifactService, useClass: ArtifactDefaultService }
|
ArtifactListPageService,
|
||||||
|
{provide: ArtifactService, useClass: ArtifactDefaultService },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ArtifactModule { }
|
export class ArtifactModule { }
|
||||||
|
@ -17,7 +17,6 @@ import { Icon } from "ng-swagger-gen/models/icon";
|
|||||||
export abstract class ArtifactService {
|
export abstract class ArtifactService {
|
||||||
reference: string[];
|
reference: string[];
|
||||||
triggerUploadArtifact = new Subject<string>();
|
triggerUploadArtifact = new Subject<string>();
|
||||||
TriggerArtifactChan$ = this.triggerUploadArtifact.asObservable();
|
|
||||||
abstract getIcon(digest: string): SafeUrl;
|
abstract getIcon(digest: string): SafeUrl;
|
||||||
abstract setIcon(digest: string, url: SafeUrl);
|
abstract setIcon(digest: string, url: SafeUrl);
|
||||||
abstract getIconsFromBackEnd(artifactList: Artifact[]);
|
abstract getIconsFromBackEnd(artifactList: Artifact[]);
|
||||||
@ -26,7 +25,6 @@ export abstract class ArtifactService {
|
|||||||
export class ArtifactDefaultService extends ArtifactService {
|
export class ArtifactDefaultService extends ArtifactService {
|
||||||
|
|
||||||
triggerUploadArtifact = new Subject<string>();
|
triggerUploadArtifact = new Subject<string>();
|
||||||
TriggerArtifactChan$ = this.triggerUploadArtifact.asObservable();
|
|
||||||
private _iconMap: {[key: string]: SafeUrl} = {};
|
private _iconMap: {[key: string]: SafeUrl} = {};
|
||||||
private _sharedIconObservableMap: {[key: string]: Observable<Icon>} = {};
|
private _sharedIconObservableMap: {[key: string]: Observable<Icon>} = {};
|
||||||
constructor(private iconService: IconService,
|
constructor(private iconService: IconService,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Accessory } from "ng-swagger-gen/models/accessory";
|
||||||
import { Artifact } from "../../../../../../ng-swagger-gen/models/artifact";
|
import { Artifact } from "../../../../../../ng-swagger-gen/models/artifact";
|
||||||
import { Platform } from "../../../../../../ng-swagger-gen/models/platform";
|
import { Platform } from "../../../../../../ng-swagger-gen/models/platform";
|
||||||
|
|
||||||
@ -5,8 +6,16 @@ export interface ArtifactFront extends Artifact {
|
|||||||
platform?: Platform;
|
platform?: Platform;
|
||||||
showImage?: string;
|
showImage?: string;
|
||||||
pullCommand?: string;
|
pullCommand?: string;
|
||||||
annotationsArray?: Array<{[key: string]: any}>;
|
annotationsArray?: Array<{ [key: string]: any }>;
|
||||||
tagNumber?: number;
|
tagNumber?: number;
|
||||||
|
coSigned?: string;
|
||||||
|
accessoryNumber?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccessoryFront extends Accessory {
|
||||||
|
pullCommand?: string;
|
||||||
|
tagNumber?: number;
|
||||||
|
scan_overview?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutipleFilter = [
|
export const mutipleFilter = [
|
||||||
@ -51,15 +60,24 @@ export const mutipleFilter = [
|
|||||||
filterByShowText: 'Label',
|
filterByShowText: 'Label',
|
||||||
listItem: []
|
listItem: []
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
export const artifactImages = [
|
|
||||||
|
export enum AccessoryType {
|
||||||
|
COSIGN = 'signature.cosign'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const artifactImages = [
|
||||||
'IMAGE', 'CHART', 'CNAB', 'OPENPOLICYAGENT'
|
'IMAGE', 'CHART', 'CNAB', 'OPENPOLICYAGENT'
|
||||||
];
|
];
|
||||||
export const artifactPullCommands = [
|
export const artifactPullCommands = [
|
||||||
{
|
{
|
||||||
type: artifactImages[0],
|
type: artifactImages[0],
|
||||||
pullCommand: 'docker pull'
|
pullCommand: 'docker pull'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: AccessoryType.COSIGN,
|
||||||
|
pullCommand: 'docker pull'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: artifactImages[1],
|
type: artifactImages[1],
|
||||||
pullCommand: 'helm chart pull'
|
pullCommand: 'helm chart pull'
|
||||||
@ -68,5 +86,8 @@ export const mutipleFilter = [
|
|||||||
type: artifactImages[2],
|
type: artifactImages[2],
|
||||||
pullCommand: 'cnab-to-oci pull'
|
pullCommand: 'cnab-to-oci pull'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
export const artifactDefault = "images/artifact-default.svg";
|
export const artifactDefault = "images/artifact-default.svg";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,7 +48,9 @@ export const enum ConfirmationTargets {
|
|||||||
P2P_PROVIDER_DELETE,
|
P2P_PROVIDER_DELETE,
|
||||||
PROJECT_ROBOT_ACCOUNT,
|
PROJECT_ROBOT_ACCOUNT,
|
||||||
PROJECT_ROBOT_ACCOUNT_ENABLE_OR_DISABLE,
|
PROJECT_ROBOT_ACCOUNT_ENABLE_OR_DISABLE,
|
||||||
WEBHOOK
|
WEBHOOK,
|
||||||
|
ACCESSORY,
|
||||||
|
ALL_ACCESSORIES
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ActionType {
|
export const enum ActionType {
|
||||||
|
@ -1705,5 +1705,21 @@
|
|||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart Label"
|
"HELM_LABEL": "Helm Chart Label"
|
||||||
|
},
|
||||||
|
"ACCESSORY": {
|
||||||
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
"DELETION_SUMMARY_ACCESSORY": "Do you want to delete all the accessories of the artifact {{param}}?",
|
||||||
|
"DELETION_SUMMARY_ONE_ACCESSORY": "Do you want to delete the accessory(s) {{param}}?",
|
||||||
|
"DELETE_ACCESSORY": "Delete Accessory",
|
||||||
|
"DELETED_SUCCESS": "Accessory deleted successfully",
|
||||||
|
"DELETED_FAILED": "Deleting accessory failed",
|
||||||
|
"CO_SIGNED": "Co-signed",
|
||||||
|
"NOTARY_SIGNED": "Notary signed",
|
||||||
|
"ACCESSORY": "Accessory",
|
||||||
|
"ACCESSORIES": "Accessories",
|
||||||
|
"SUBJECT_ARTIFACT": "Subject Artifact",
|
||||||
|
"CO_SIGN": "Co-sign",
|
||||||
|
"NOTARY": "Notary",
|
||||||
|
"PLACEHOLDER": "We couldn't find any accessories!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1705,5 +1705,21 @@
|
|||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label"
|
||||||
|
},
|
||||||
|
"ACCESSORY": {
|
||||||
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
"DELETION_SUMMARY_ACCESSORY": "Do you want to delete all the accessories of the artifact {{param}}?",
|
||||||
|
"DELETION_SUMMARY_ONE_ACCESSORY": "Do you want to delete the accessory(s) {{param}}?",
|
||||||
|
"DELETE_ACCESSORY": "Delete Accessory",
|
||||||
|
"DELETED_SUCCESS": "Accessory deleted successfully",
|
||||||
|
"DELETED_FAILED": "Deleting accessory failed",
|
||||||
|
"CO_SIGNED": "Co-signed",
|
||||||
|
"NOTARY_SIGNED": "Notary signed",
|
||||||
|
"ACCESSORY": "Accessory",
|
||||||
|
"ACCESSORIES": "Accessories",
|
||||||
|
"SUBJECT_ARTIFACT": "Subject Artifact",
|
||||||
|
"CO_SIGN": "Co-sign",
|
||||||
|
"NOTARY": "Notary",
|
||||||
|
"PLACEHOLDER": "We couldn't find any accessories!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1704,5 +1704,21 @@
|
|||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label"
|
||||||
|
},
|
||||||
|
"ACCESSORY": {
|
||||||
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
"DELETION_SUMMARY_ACCESSORY": "Do you want to delete all the accessories of the artifact {{param}}?",
|
||||||
|
"DELETION_SUMMARY_ONE_ACCESSORY": "Do you want to delete the accessory(s) {{param}}?",
|
||||||
|
"DELETE_ACCESSORY": "Delete Accessory",
|
||||||
|
"DELETED_SUCCESS": "Accessory deleted successfully",
|
||||||
|
"DELETED_FAILED": "Deleting accessory failed",
|
||||||
|
"CO_SIGNED": "Co-signed",
|
||||||
|
"NOTARY_SIGNED": "Notary signed",
|
||||||
|
"ACCESSORY": "Accessory",
|
||||||
|
"ACCESSORIES": "Accessories",
|
||||||
|
"SUBJECT_ARTIFACT": "Subject Artifact",
|
||||||
|
"CO_SIGN": "Co-sign",
|
||||||
|
"NOTARY": "Notary",
|
||||||
|
"PLACEHOLDER": "We couldn't find any accessories!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1673,5 +1673,21 @@
|
|||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label"
|
||||||
|
},
|
||||||
|
"ACCESSORY": {
|
||||||
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
"DELETION_SUMMARY_ACCESSORY": "Do you want to delete all the accessories of the artifact {{param}}?",
|
||||||
|
"DELETION_SUMMARY_ONE_ACCESSORY": "Do you want to delete the accessory(s) {{param}}?",
|
||||||
|
"DELETE_ACCESSORY": "Delete Accessory",
|
||||||
|
"DELETED_SUCCESS": "Accessory deleted successfully",
|
||||||
|
"DELETED_FAILED": "Deleting accessory failed",
|
||||||
|
"CO_SIGNED": "Co-signed",
|
||||||
|
"NOTARY_SIGNED": "Notary signed",
|
||||||
|
"ACCESSORY": "Accessory",
|
||||||
|
"ACCESSORIES": "Accessories",
|
||||||
|
"SUBJECT_ARTIFACT": "Subject Artifact",
|
||||||
|
"CO_SIGN": "Co-sign",
|
||||||
|
"NOTARY": "Notary",
|
||||||
|
"PLACEHOLDER": "We couldn't find any accessories!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1701,5 +1701,21 @@
|
|||||||
"LIST": "Listar",
|
"LIST": "Listar",
|
||||||
"REPOSITORY": "Repositório",
|
"REPOSITORY": "Repositório",
|
||||||
"HELM_LABEL": "Marcador do Helm Chart"
|
"HELM_LABEL": "Marcador do Helm Chart"
|
||||||
|
},
|
||||||
|
"ACCESSORY": {
|
||||||
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
"DELETION_SUMMARY_ACCESSORY": "Do you want to delete all the accessories of the artifact {{param}}?",
|
||||||
|
"DELETION_SUMMARY_ONE_ACCESSORY": "Do you want to delete the accessory(s) {{param}}?",
|
||||||
|
"DELETE_ACCESSORY": "Delete Accessory",
|
||||||
|
"DELETED_SUCCESS": "Accessory deleted successfully",
|
||||||
|
"DELETED_FAILED": "Deleting accessory failed",
|
||||||
|
"CO_SIGNED": "Co-signed",
|
||||||
|
"NOTARY_SIGNED": "Notary signed",
|
||||||
|
"ACCESSORY": "Accessory",
|
||||||
|
"ACCESSORIES": "Accessories",
|
||||||
|
"SUBJECT_ARTIFACT": "Subject Artifact",
|
||||||
|
"CO_SIGN": "Co-sign",
|
||||||
|
"NOTARY": "Notary",
|
||||||
|
"PLACEHOLDER": "We couldn't find any accessories!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1705,5 +1705,21 @@
|
|||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label"
|
||||||
|
},
|
||||||
|
"ACCESSORY": {
|
||||||
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
"DELETION_SUMMARY_ACCESSORY": "Do you want to delete all the accessories of the artifact {{param}}?",
|
||||||
|
"DELETION_SUMMARY_ONE_ACCESSORY": "Do you want to delete the accessory(s) {{param}}?",
|
||||||
|
"DELETE_ACCESSORY": "Delete Accessory",
|
||||||
|
"DELETED_SUCCESS": "Accessory deleted successfully",
|
||||||
|
"DELETED_FAILED": "Deleting accessory failed",
|
||||||
|
"CO_SIGNED": "Co-signed",
|
||||||
|
"NOTARY_SIGNED": "Notary signed",
|
||||||
|
"ACCESSORY": "Accessory",
|
||||||
|
"ACCESSORIES": "Accessories",
|
||||||
|
"SUBJECT_ARTIFACT": "Subject Artifact",
|
||||||
|
"CO_SIGN": "Co-sign",
|
||||||
|
"NOTARY": "Notary",
|
||||||
|
"PLACEHOLDER": "We couldn't find any accessories!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,13 +129,13 @@
|
|||||||
"ADMIN_RENAME_TIP": "单击将用户名改为 \"admin@harbor.local\", 注意这个操作是无法撤销的",
|
"ADMIN_RENAME_TIP": "单击将用户名改为 \"admin@harbor.local\", 注意这个操作是无法撤销的",
|
||||||
"RENAME_SUCCESS": "用户名更改成功!",
|
"RENAME_SUCCESS": "用户名更改成功!",
|
||||||
"ADMIN_RENAME_BUTTON": "更改用户名",
|
"ADMIN_RENAME_BUTTON": "更改用户名",
|
||||||
"RENAME_CONFIRM_INFO": "更改用户名为admin@harbor.local是无法撤销的, 你确定更改吗?",
|
"RENAME_CONFIRM_INFO": "更改用户名为admin@harbor.local是无法撤销的, 您确定更改吗?",
|
||||||
"CLI_PASSWORD": "CLI密码",
|
"CLI_PASSWORD": "CLI密码",
|
||||||
"CLI_PASSWORD_TIP": "使用docker/helm cli访问Harbor时,可以使用此cli密码作为密码。",
|
"CLI_PASSWORD_TIP": "使用docker/helm cli访问Harbor时,可以使用此cli密码作为密码。",
|
||||||
"COPY_SUCCESS": "复制成功",
|
"COPY_SUCCESS": "复制成功",
|
||||||
"COPY_ERROR": "复制失败",
|
"COPY_ERROR": "复制失败",
|
||||||
"ADMIN_CLI_SECRET_BUTTON": "生成新的CLI密码",
|
"ADMIN_CLI_SECRET_BUTTON": "生成新的CLI密码",
|
||||||
"ADMIN_CLI_SECRET_RESET_BUTTON": "输入你自己的CLI密码",
|
"ADMIN_CLI_SECRET_RESET_BUTTON": "输入您自己的CLI密码",
|
||||||
"NEW_SECRET": "密码",
|
"NEW_SECRET": "密码",
|
||||||
"CONFIRM_SECRET": "重复出入密码",
|
"CONFIRM_SECRET": "重复出入密码",
|
||||||
"GENERATE_SUCCESS": "成功设置新的CLI密码",
|
"GENERATE_SUCCESS": "成功设置新的CLI密码",
|
||||||
@ -200,7 +200,7 @@
|
|||||||
"ADD_USER_TITLE": "创建用户",
|
"ADD_USER_TITLE": "创建用户",
|
||||||
"SAVE_SUCCESS": "成功创建用户。",
|
"SAVE_SUCCESS": "成功创建用户。",
|
||||||
"DELETION_TITLE": "删除用户确认",
|
"DELETION_TITLE": "删除用户确认",
|
||||||
"DELETION_SUMMARY": "你确认删除用户 {{param}}?",
|
"DELETION_SUMMARY": "您确认删除用户 {{param}}?",
|
||||||
"DELETE_SUCCESS": "成功删除用户。",
|
"DELETE_SUCCESS": "成功删除用户。",
|
||||||
"OF": "共计",
|
"OF": "共计",
|
||||||
"ITEMS": "条记录",
|
"ITEMS": "条记录",
|
||||||
@ -236,7 +236,7 @@
|
|||||||
"OF": "共计",
|
"OF": "共计",
|
||||||
"ITEMS": "条记录",
|
"ITEMS": "条记录",
|
||||||
"DELETION_TITLE": "移除项目成员确认",
|
"DELETION_TITLE": "移除项目成员确认",
|
||||||
"DELETION_SUMMARY": "你确认删除项目 {{param}}?",
|
"DELETION_SUMMARY": "您确认删除项目 {{param}}?",
|
||||||
"FILTER_PLACEHOLDER": "过滤项目",
|
"FILTER_PLACEHOLDER": "过滤项目",
|
||||||
"REPLICATION_RULE": "复制规则",
|
"REPLICATION_RULE": "复制规则",
|
||||||
"CREATED_SUCCESS": "成功创建项目。",
|
"CREATED_SUCCESS": "成功创建项目。",
|
||||||
@ -248,7 +248,7 @@
|
|||||||
"STORAGE_QUOTA": "存储容量",
|
"STORAGE_QUOTA": "存储容量",
|
||||||
"COUNT_QUOTA_TIP": "请输入一个'1' ~ '100000000'之间的整数, '-1'表示不设置上限。",
|
"COUNT_QUOTA_TIP": "请输入一个'1' ~ '100000000'之间的整数, '-1'表示不设置上限。",
|
||||||
"STORAGE_QUOTA_TIP": "存储配额的上限仅采用整数值,上限为1024TB。输入“-1”作为无限制配额。",
|
"STORAGE_QUOTA_TIP": "存储配额的上限仅采用整数值,上限为1024TB。输入“-1”作为无限制配额。",
|
||||||
"QUOTA_UNLIMIT_TIP": "如果你想要对存储不设置上限,请输入-1。",
|
"QUOTA_UNLIMIT_TIP": "如果您想要对存储不设置上限,请输入-1。",
|
||||||
"TYPE": "类型",
|
"TYPE": "类型",
|
||||||
"PROXY_CACHE": "镜像代理",
|
"PROXY_CACHE": "镜像代理",
|
||||||
"PROXY_CACHE_TOOLTIP": "开启此项,以使得该项目成为目标仓库的镜像代理.仅支持 DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay 和 Google GCR 类型的仓库",
|
"PROXY_CACHE_TOOLTIP": "开启此项,以使得该项目成为目标仓库的镜像代理.仅支持 DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay 和 Google GCR 类型的仓库",
|
||||||
@ -325,13 +325,13 @@
|
|||||||
"UNKNOWN_ERROR": "添加成员时发生未知错误",
|
"UNKNOWN_ERROR": "添加成员时发生未知错误",
|
||||||
"FILTER_PLACEHOLDER": "过滤成员",
|
"FILTER_PLACEHOLDER": "过滤成员",
|
||||||
"DELETION_TITLE": "删除项目成员确认",
|
"DELETION_TITLE": "删除项目成员确认",
|
||||||
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?",
|
"DELETION_SUMMARY": "您确认删除项目成员 {{param}}?",
|
||||||
"ADDED_SUCCESS": "成功新增成员",
|
"ADDED_SUCCESS": "成功新增成员",
|
||||||
"DELETED_SUCCESS": "成功删除成员",
|
"DELETED_SUCCESS": "成功删除成员",
|
||||||
"SWITCHED_SUCCESS": "切换角色成功",
|
"SWITCHED_SUCCESS": "切换角色成功",
|
||||||
"OF": "共计",
|
"OF": "共计",
|
||||||
"SWITCH_TITLE": "切换项目成员确认",
|
"SWITCH_TITLE": "切换项目成员确认",
|
||||||
"SWITCH_SUMMARY": "你确认切换项目成员 {{param}}??",
|
"SWITCH_SUMMARY": "您确认切换项目成员 {{param}}??",
|
||||||
"SET_ROLE": "设置角色",
|
"SET_ROLE": "设置角色",
|
||||||
"REMOVE": "移除成员",
|
"REMOVE": "移除成员",
|
||||||
"GROUP_NAME_REQUIRED": "组名称为必填项",
|
"GROUP_NAME_REQUIRED": "组名称为必填项",
|
||||||
@ -368,7 +368,7 @@
|
|||||||
"CREATED_SUCCESS": "创建账户 '{{param}}' 成功。",
|
"CREATED_SUCCESS": "创建账户 '{{param}}' 成功。",
|
||||||
"COPY_SUCCESS": "成功复制 '{{param}}' 的令牌",
|
"COPY_SUCCESS": "成功复制 '{{param}}' 的令牌",
|
||||||
"DELETION_TITLE": "删除账户确认",
|
"DELETION_TITLE": "删除账户确认",
|
||||||
"DELETION_SUMMARY": "你确认删除机器人账户 {{param}}?",
|
"DELETION_SUMMARY": "您确认删除机器人账户 {{param}}?",
|
||||||
"PULL_IS_MUST": "拉取权限默认选中且不可修改。",
|
"PULL_IS_MUST": "拉取权限默认选中且不可修改。",
|
||||||
"EXPORT_TO_FILE": "导出到文件中",
|
"EXPORT_TO_FILE": "导出到文件中",
|
||||||
"EXPIRES_AT": "到期日",
|
"EXPIRES_AT": "到期日",
|
||||||
@ -1219,7 +1219,7 @@
|
|||||||
"UNKNOWN_ERROR": "发生未知错误,请稍后再试。",
|
"UNKNOWN_ERROR": "发生未知错误,请稍后再试。",
|
||||||
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
|
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
|
||||||
"REPO_READ_ONLY": "Harbor 被设置为只读模式,在此模式下,不能删除仓库、artifact、 Tag 及推送镜像。",
|
"REPO_READ_ONLY": "Harbor 被设置为只读模式,在此模式下,不能删除仓库、artifact、 Tag 及推送镜像。",
|
||||||
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限。",
|
"FORBIDDEN_ERROR": "当前操作被禁止,请确认您有合法的权限。",
|
||||||
"GENERAL_ERROR": "调用后台服务时出现错误: {{param}}。",
|
"GENERAL_ERROR": "调用后台服务时出现错误: {{param}}。",
|
||||||
"BAD_REQUEST_ERROR": "错误请求, 操作无法完成。",
|
"BAD_REQUEST_ERROR": "错误请求, 操作无法完成。",
|
||||||
"NOT_FOUND_ERROR": "对象不存在, 请求无法完成。",
|
"NOT_FOUND_ERROR": "对象不存在, 请求无法完成。",
|
||||||
@ -1504,11 +1504,11 @@
|
|||||||
"SETUP_TIMESTAMP": "创建时间",
|
"SETUP_TIMESTAMP": "创建时间",
|
||||||
"PROVIDER": "供应商",
|
"PROVIDER": "供应商",
|
||||||
"DELETION_TITLE": "删除实例",
|
"DELETION_TITLE": "删除实例",
|
||||||
"DELETION_SUMMARY": "你确认删除实例 {{param}}?",
|
"DELETION_SUMMARY": "您确认删除实例 {{param}}?",
|
||||||
"ENABLE_TITLE": "启用实例",
|
"ENABLE_TITLE": "启用实例",
|
||||||
"ENABLE_SUMMARY": "你确认启用实例 {{param}}?",
|
"ENABLE_SUMMARY": "您确认启用实例 {{param}}?",
|
||||||
"DISABLE_TITLE": "禁用实例",
|
"DISABLE_TITLE": "禁用实例",
|
||||||
"DISABLE_SUMMARY": "你确认禁用实例 {{param}}?",
|
"DISABLE_SUMMARY": "您确认禁用实例 {{param}}?",
|
||||||
"IMAGE": "镜像",
|
"IMAGE": "镜像",
|
||||||
"START_TIME": "开始时间",
|
"START_TIME": "开始时间",
|
||||||
"FINISH_TIME": "完成时间",
|
"FINISH_TIME": "完成时间",
|
||||||
@ -1645,7 +1645,7 @@
|
|||||||
"ENABLE_TITLE": "启用机器人",
|
"ENABLE_TITLE": "启用机器人",
|
||||||
"ENABLE_SUMMARY": "您想启用机器人 {{param}}?",
|
"ENABLE_SUMMARY": "您想启用机器人 {{param}}?",
|
||||||
"DISABLE_TITLE": "禁用机器人",
|
"DISABLE_TITLE": "禁用机器人",
|
||||||
"DISABLE_SUMMARY": "你想禁用机器人 {{param}}?",
|
"DISABLE_SUMMARY": "您想禁用机器人 {{param}}?",
|
||||||
"ENABLE_ROBOT_SUCCESSFULLY": "启用机器人成功",
|
"ENABLE_ROBOT_SUCCESSFULLY": "启用机器人成功",
|
||||||
"DISABLE_ROBOT_SUCCESSFULLY": "禁用机器人成功",
|
"DISABLE_ROBOT_SUCCESSFULLY": "禁用机器人成功",
|
||||||
"ROBOT_ACCOUNT": "机器人账户",
|
"ROBOT_ACCOUNT": "机器人账户",
|
||||||
@ -1703,5 +1703,21 @@
|
|||||||
"LIST": "查询",
|
"LIST": "查询",
|
||||||
"REPOSITORY": "仓库",
|
"REPOSITORY": "仓库",
|
||||||
"HELM_LABEL": "Helm Chart 标签"
|
"HELM_LABEL": "Helm Chart 标签"
|
||||||
|
},
|
||||||
|
"ACCESSORY": {
|
||||||
|
"DELETION_TITLE_ACCESSORY": "删除附件确认",
|
||||||
|
"DELETION_SUMMARY_ACCESSORY": "您确定要删除 Artifact {{param}} 的所有附件吗?",
|
||||||
|
"DELETION_SUMMARY_ONE_ACCESSORY": "您确定要删除附件 {{param}} ?",
|
||||||
|
"DELETE_ACCESSORY": "删除附件",
|
||||||
|
"DELETED_SUCCESS": "删除附件成功",
|
||||||
|
"DELETED_FAILED": "删除附件失败",
|
||||||
|
"CO_SIGNED": "Co-sign 签名",
|
||||||
|
"NOTARY_SIGNED": "Notary 签名",
|
||||||
|
"ACCESSORY": "附件",
|
||||||
|
"ACCESSORIES": "附件",
|
||||||
|
"SUBJECT_ARTIFACT": "主体 Artifact",
|
||||||
|
"CO_SIGN": "Co-sign",
|
||||||
|
"NOTARY": "Notary",
|
||||||
|
"PLACEHOLDER": "未发现任何附件!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1690,5 +1690,21 @@
|
|||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label"
|
||||||
|
},
|
||||||
|
"ACCESSORY": {
|
||||||
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
"DELETION_SUMMARY_ACCESSORY": "Do you want to delete all the accessories of the artifact {{param}}?",
|
||||||
|
"DELETION_SUMMARY_ONE_ACCESSORY": "Do you want to delete the accessory(s) {{param}}?",
|
||||||
|
"DELETE_ACCESSORY": "Delete Accessory",
|
||||||
|
"DELETED_SUCCESS": "Accessory deleted successfully",
|
||||||
|
"DELETED_FAILED": "Deleting accessory failed",
|
||||||
|
"CO_SIGNED": "Co-signed",
|
||||||
|
"NOTARY_SIGNED": "Notary signed",
|
||||||
|
"ACCESSORY": "Accessory",
|
||||||
|
"ACCESSORIES": "Accessories",
|
||||||
|
"SUBJECT_ARTIFACT": "Subject Artifact",
|
||||||
|
"CO_SIGN": "Co-sign",
|
||||||
|
"NOTARY": "Notary",
|
||||||
|
"PLACEHOLDER": "We couldn't find any accessories!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user