Commit 2553ee38 authored by Yogi_Wang's avatar Yogi_Wang
Browse files

Oci ui include artifact list and artifact summary


Signed-off-by: default avatarYogi_Wang <yawang@vmware.com>
Signed-off-by: default avatarAllForNothing <sshijun@vmware.com>
Signed-off-by: default avatarYogi_Wang <yawang@vmware.com>
parent e03e3e33
......@@ -18,6 +18,7 @@ import { SystemAdminGuard } from './shared/route/system-admin-activate.service';
import { AuthCheckGuard } from './shared/route/auth-user-activate.service';
import { SignInGuard } from './shared/route/sign-in-guard-activate.service';
import { MemberGuard } from './shared/route/member-guard-activate.service';
import { ArtifactGuard } from './shared/route/artifact-guard-activate.service';
import { MemberPermissionGuard } from './shared/route/member-permission-guard-activate.service';
import { OidcGuard } from './shared/route/oidc-guard-active.service';
......@@ -42,8 +43,8 @@ import { AuditLogComponent } from './log/audit-log.component';
import { LogPageComponent } from './log/log-page.component';
import { RepositoryPageComponent } from './repository/repository-page.component';
import { TagRepositoryComponent } from './repository/tag-repository/tag-repository.component';
import { TagDetailPageComponent } from './repository/tag-detail/tag-detail-page.component';
import { ArtifactListPageComponent } from './repository/artifact-list-page/artifact-list-page.component';
import { ArtifactSummaryPageComponent } from './repository/artifact-summary-page/artifact-summary-page.component';
import { LeavingRepositoryRouteDeactivate } from './shared/route/leaving-repository-deactivate.service';
import { ProjectComponent } from './project/project.component';
......@@ -71,6 +72,7 @@ import { LabelsComponent } from "./labels/labels.component";
import { ProjectQuotasComponent } from "./project-quotas/project-quotas.component";
import { VulnerabilityConfigComponent } from "../lib/components/config/vulnerability/vulnerability-config.component";
import { USERSTATICPERMISSION } from "../lib/services";
import { LeavingArtifactSummaryRouteDeactivate } from './shared/route/leaving-artifact-summary-deactivate.service';
const harborRoutes: Routes = [
......@@ -169,7 +171,7 @@ const harborRoutes: Routes = [
},
{
path: 'tags/:id/:repo',
component: TagRepositoryComponent,
component: ArtifactListPageComponent,
canActivate: [MemberGuard],
resolve: {
projectResolver: ProjectRoutingResolver
......@@ -177,17 +179,27 @@ const harborRoutes: Routes = [
},
{
path: 'projects/:id/repositories/:repo',
component: TagRepositoryComponent,
component: ArtifactListPageComponent,
canActivate: [MemberGuard],
canDeactivate: [LeavingRepositoryRouteDeactivate],
resolve: {
projectResolver: ProjectRoutingResolver
}
},
},
{
path: 'projects/:id/repositories/:repo/tags/:tag',
component: TagDetailPageComponent,
path: 'projects/:id/repositories/:repo/depth/:depth',
component: ArtifactListPageComponent,
canActivate: [MemberGuard],
canDeactivate: [LeavingRepositoryRouteDeactivate],
resolve: {
projectResolver: ProjectRoutingResolver
},
},
{
path: 'projects/:id/repositories/:repo/artifacts/:digest',
component: ArtifactSummaryPageComponent,
canActivate: [MemberGuard, ArtifactGuard],
canDeactivate: [LeavingArtifactSummaryRouteDeactivate],
resolve: {
projectResolver: ProjectRoutingResolver
}
......@@ -258,7 +270,7 @@ const harborRoutes: Routes = [
action: USERSTATICPERMISSION.REPOSITORY.VALUE.LIST
}
},
component: TagRepositoryComponent
component: ArtifactListPageComponent
},
{
path: 'members',
......
......@@ -5,6 +5,7 @@ import { debounceTime } from 'rxjs/operators';
import { HelmChartVersion } from '../helm-chart.interface.service';
import { ResourceType } from '../../../shared/shared.const';
import { Label, Tag } from "../../../../lib/services";
import { Artifact } from '../../../../lib/components/artifact/artifact';
@Component({
selector: "hbr-chart-version-label-filter",
......@@ -46,7 +47,7 @@ export class LabelFilterComponent implements ClrDatagridFilterInterface<any>, On
if (this.resourceType === ResourceType.CHART_VERSION) {
return (cv as HelmChartVersion).labels.some(label => this.selectedLabels.get(label.id));
} else if (this.resourceType === ResourceType.REPOSITORY_TAG) {
return (cv as Tag).labels.some(label => this.selectedLabels.get(label.id));
return (cv as Artifact).labels.some(label => this.selectedLabels.get(label.id));
} else {
return true;
}
......
......@@ -3,8 +3,13 @@
<a (click)="goProBack()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
<span class="back-icon"><</span>
<a (click)="watchGoBackEvt(projectId)">{{'REPOSITORY.REPOSITORIES'| translate}}</a>
<span *ngIf="referArtifactNameArray.length===1">&lt;<a (click)="backInitRepo()">{{repoName}}</a></span>
<span *ngIf="referArtifactNameArray.length>=2" >
<span *ngFor="let digest of referArtifactNameArray;let i = index">
&lt;<a (click)="jumpDigest(referArtifactNameArray,i)" >{{digest | slice:0:15}}</a></span>
</span>
</div>
<hbr-repository [repoName]="repoName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole"
<artifact-list [repoName]="repoName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole"
[projectId]="projectId" [memberRoleID]="projectMemberRoleId" [isGuest]="isGuest"
(tagClickEvent)="watchTagClickEvt($event)" (backEvt)="watchGoBackEvt($event)"></hbr-repository>
(tagClickEvent)="watchTagClickEvt($event)" (backEvt)="watchGoBackEvt($event)" (putArtifactReferenceArr)="putArtifactReferenceArr($event)"></artifact-list>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { TagRepositoryComponent } from './tag-repository.component';
import { ArtifactListPageComponent } from './artifact-list-page.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ClarityModule } from '@clr/angular';
......@@ -12,9 +12,10 @@ import { ActivatedRoute, Router } from '@angular/router';
import { AppConfigService } from '../../app-config.service';
import { SessionService } from '../../shared/session.service';
describe('TagRepositoryComponent', () => {
let component: TagRepositoryComponent;
let fixture: ComponentFixture<TagRepositoryComponent>;
import { ArtifactService } from '../../../lib/services';
describe('ArtifactListPageComponent', () => {
let component: ArtifactListPageComponent;
let fixture: ComponentFixture<ArtifactListPageComponent>;
const mockSessionService = {
getCurrentUser: () => { }
};
......@@ -33,6 +34,11 @@ describe('TagRepositoryComponent', () => {
const mockRouter = {
navigate: () => { }
};
const mockArtifactService = {
triggerUploadArtifact: {
next: () => {}
}
};
const mockActivatedRoute = {
RouterparamMap: of({ get: (key) => 'value' }),
snapshot: {
......@@ -69,20 +75,21 @@ describe('TagRepositoryComponent', () => {
NoopAnimationsModule,
HttpClientTestingModule
],
declarations: [TagRepositoryComponent],
declarations: [ArtifactListPageComponent],
providers: [
TranslateService,
{ provide: SessionService, useValue: mockSessionService },
{ provide: AppConfigService, useValue: mockAppConfigService },
{ provide: Router, useValue: mockRouter },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: ArtifactService, useValue: mockArtifactService },
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TagRepositoryComponent);
fixture = TestBed.createComponent(ArtifactListPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
......
......@@ -16,29 +16,32 @@ import { ActivatedRoute, Router } from '@angular/router';
import { AppConfigService } from '../../app-config.service';
import { SessionService } from '../../shared/session.service';
import { Project } from '../../project/project';
import { RepositoryComponent } from "../../../lib/components/repository/repository.component";
import { TagClickEvent } from "../../../lib/services";
import { ArtifactListComponent } from "../../../lib/components/artifact-list/artifact-list.component";
import { ArtifactClickEvent, ArtifactService } from "../../../lib/services";
import { clone } from '../../../lib/utils/utils';
@Component({
selector: 'tag-repository',
templateUrl: 'tag-repository.component.html',
styleUrls: ['./tag-repository.component.scss']
selector: 'artifact-list-page',
templateUrl: 'artifact-list-page.component.html',
styleUrls: ['./artifact-list-page.component.scss']
})
export class TagRepositoryComponent implements OnInit {
export class ArtifactListPageComponent implements OnInit {
projectId: number;
projectMemberRoleId: number;
repoName: string;
referArtifactNameArray: string[] = [];
hasProjectAdminRole: boolean = false;
isGuest: boolean;
registryUrl: string;
@ViewChild(RepositoryComponent, {static: false})
repositoryComponent: RepositoryComponent;
@ViewChild(ArtifactListComponent, {static: false})
repositoryComponent: ArtifactListComponent;
constructor(
private route: ActivatedRoute,
private router: Router,
private artifactService: ArtifactService,
private appConfigService: AppConfigService,
private session: SessionService) {
}
......@@ -57,8 +60,12 @@ export class TagRepositoryComponent implements OnInit {
this.projectMemberRoleId = (<Project>resolverData['projectResolver']).current_user_role_id;
}
this.repoName = this.route.snapshot.params['repo'];
this.registryUrl = this.appConfigService.getConfig().registry_url;
let refer = JSON.parse(sessionStorage.getItem('reference'));
if (refer && refer.projectId === this.projectId && refer.repo === this.repoName) {
this.referArtifactNameArray = refer.referArray;
}
}
get withNotary(): boolean {
......@@ -76,8 +83,13 @@ export class TagRepositoryComponent implements OnInit {
return this.repositoryComponent.hasChanges();
}
watchTagClickEvt(tagEvt: TagClickEvent): void {
let linkUrl = ['harbor', 'projects', tagEvt.project_id, 'repositories', tagEvt.repository_name, 'tags', tagEvt.tag_name];
watchTagClickEvt(artifactEvt: ArtifactClickEvent): void {
//
sessionStorage.setItem('referenceSummary', JSON.stringify({ projectId: this.projectId, repo: this.repoName,
"digest": artifactEvt.digest, referArray: this.referArtifactNameArray}));
let linkUrl = ['harbor', 'projects', artifactEvt.project_id, 'repositories'
, artifactEvt.repository_name, 'artifacts', artifactEvt.digest];
this.router.navigate(linkUrl);
}
......@@ -87,4 +99,26 @@ export class TagRepositoryComponent implements OnInit {
goProBack(): void {
this.router.navigate(["harbor", "projects"]);
}
backInitRepo() {
this.referArtifactNameArray = [];
sessionStorage.removeItem('reference');
this.updateArtifactList('repoName');
}
jumpDigest(referArtifactNameArray: string[], index: number) {
this.referArtifactNameArray = referArtifactNameArray.slice(index);
this.referArtifactNameArray.pop();
this.referArtifactNameArray = referArtifactNameArray.slice(index);
sessionStorage.setItem('reference', JSON.stringify({ projectId: this.projectId, repo: this.repoName,
referArray: referArtifactNameArray.slice(index)}));
this.updateArtifactList(referArtifactNameArray.slice(index));
}
updateArtifactList(res): void {
this.artifactService.triggerUploadArtifact.next(res);
}
putArtifactReferenceArr(digestArray) {
this.referArtifactNameArray = digestArray;
}
}
......@@ -4,11 +4,14 @@
<span class="back-icon"><</span>
<a (click)="goBackRep()">{{'REPOSITORY.REPOSITORIES'| translate}}</a>
<span class="back-icon"><</span>
<a (click)="goBack(repositoryId)">{{repositoryId}}</a>
<a (click)="goBack(repositoryName)">{{'REPOSITORY.ARTIFACTS'| translate}}</a>
<span *ngFor="let digest of referArtifactNameArray;let i = index">
&lt;<a (click)="jumpDigest(referArtifactNameArray,i)" >{{digest | slice:0:15}}</a></span>
</div>
<hbr-tag-detail (backEvt)="goBack($event)"
[tagId]="tagId"
<artifact-summary (backEvt)="goBack($event)"
[artifactDigest]="artifactDigest"
[withAdmiral]="withAdmiral"
[projectId]="projectId"
[repositoryId]="repositoryId"></hbr-tag-detail>
[repositoryName]="repositoryName"></artifact-summary>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { TagDetailPageComponent } from './tag-detail-page.component';
import { ArtifactSummaryPageComponent } from './artifact-summary-page.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ClarityModule } from '@clr/angular';
......@@ -12,9 +12,9 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ActivatedRoute, Router } from '@angular/router';
import {AppConfigService} from "../../app-config.service";
import { SessionService } from '../../shared/session.service';
describe('TagDetailPageComponent', () => {
let component: TagDetailPageComponent;
let fixture: ComponentFixture<TagDetailPageComponent>;
describe('ArtifactSummaryPageComponent', () => {
let component: ArtifactSummaryPageComponent;
let fixture: ComponentFixture<ArtifactSummaryPageComponent>;
const mockSessionService = {
getCurrentUser: () => { }
};
......@@ -68,7 +68,7 @@ describe('TagDetailPageComponent', () => {
NoopAnimationsModule,
HttpClientTestingModule
],
declarations: [TagDetailPageComponent],
declarations: [ArtifactSummaryPageComponent],
providers: [
TranslateService,
{ provide: SessionService, useValue: mockSessionService },
......@@ -81,7 +81,7 @@ describe('TagDetailPageComponent', () => {
}));
beforeEach(() => {
fixture = TestBed.createComponent(TagDetailPageComponent);
fixture = TestBed.createComponent(ArtifactSummaryPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
......
......@@ -11,41 +11,47 @@
// 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 } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {AppConfigService} from "../../app-config.service";
import { SessionService } from '../../shared/session.service';
@Component({
selector: 'repository',
templateUrl: 'tag-detail-page.component.html',
styleUrls: ["tag-detail-page.component.scss"]
selector: 'artifact-summary-page',
templateUrl: 'artifact-summary-page.component.html',
styleUrls: ["artifact-summary-page.component.scss"]
})
export class TagDetailPageComponent implements OnInit {
export class ArtifactSummaryPageComponent implements OnInit, OnDestroy {
tagId: string;
repositoryId: string;
artifactDigest: string;
repositoryName: string;
projectId: string | number;
referArtifactNameArray: string[] = [];
constructor(
private route: ActivatedRoute,
private appConfigService: AppConfigService,
private router: Router,
private session: SessionService
private router: Router
) {
}
ngOnInit(): void {
this.repositoryId = this.route.snapshot.params["repo"];
this.tagId = this.route.snapshot.params["tag"];
this.repositoryName = this.route.snapshot.params["repo"];
this.artifactDigest = this.route.snapshot.params["digest"];
this.projectId = this.route.snapshot.params["id"];
let refer = JSON.parse(sessionStorage.getItem('referenceSummary'));
if (refer && refer.projectId === this.projectId && refer.repo === this.repositoryName && refer.digest === this.artifactDigest) {
this.referArtifactNameArray = refer.referArray;
}
}
get withAdmiral(): boolean {
return this.appConfigService.getConfig().with_admiral;
}
goBack(tag: string): void {
this.router.navigate(["harbor", "projects", this.projectId, "repositories", tag]);
goBack(repositoryName: string): void {
this.router.navigate(["harbor", "projects", this.projectId, "repositories", repositoryName]);
}
goBackRep(): void {
this.router.navigate(["harbor", "projects", this.projectId, "repositories"]);
......@@ -53,4 +59,14 @@ export class TagDetailPageComponent implements OnInit {
goBackPro(): void {
this.router.navigate(["harbor", "projects"]);
}
ngOnDestroy(): void {
sessionStorage.removeItem('referenceSummary');
}
jumpDigest(referArtifactNameArray: string[], index: number) {
sessionStorage.removeItem('referenceSummary');
sessionStorage.setItem('reference', JSON.stringify({ projectId: this.projectId, repo: this.repositoryName,
referArray: referArtifactNameArray.slice(index)}));
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName]);
}
}
......@@ -46,7 +46,7 @@ export class RepositoryPageComponent implements OnInit {
}
watchRepoClickEvent(repoEvt: RepositoryItem): void {
let linkUrl = ['harbor', 'projects', repoEvt.project_id, 'repositories', repoEvt.name];
let linkUrl = ['harbor', 'projects', repoEvt.project_id, 'repositories', repoEvt.name.split('/')[1]];
this.router.navigate(linkUrl);
}
}
......@@ -17,9 +17,9 @@ import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared/shared.module';
import { RepositoryPageComponent } from './repository-page.component';
import { TagRepositoryComponent } from './tag-repository/tag-repository.component';
import { ArtifactListPageComponent } from './artifact-list-page/artifact-list-page.component';
import { TopRepoComponent } from './top-repo/top-repo.component';
import { TagDetailPageComponent } from './tag-detail/tag-detail-page.component';
import { ArtifactSummaryPageComponent } from './artifact-summary-page/artifact-summary-page.component';
@NgModule({
imports: [
......@@ -28,14 +28,14 @@ import { TagDetailPageComponent } from './tag-detail/tag-detail-page.component';
],
declarations: [
RepositoryPageComponent,
TagRepositoryComponent,
ArtifactListPageComponent,
TopRepoComponent,
TagDetailPageComponent
ArtifactSummaryPageComponent
],
exports: [
RepositoryPageComponent,
TopRepoComponent,
TagDetailPageComponent
ArtifactSummaryPageComponent
],
providers: []
})
......
<clr-datagrid (clrDgRefresh)="refresh($event)">
<clr-dg-column>{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.ARTIFACTS_COUNT' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-row *clrDgItems="let r of repositories" [clrDgItem]='r'>
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name || r.repository_name}}</a></clr-dg-cell>
......
import { TestBed } from '@angular/core/testing';
import { ArtifactGuardActivateService } from './artifact-guard-activate.service';
describe('ArtifactGuardActivateService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ArtifactGuardActivateService = TestBed.get(ArtifactGuardActivateService);
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ArtifactGuardActivateService {
constructor() { }
}
// Copyright Project Harbor Authors
//
// 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 {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot,
CanActivateChild
} from '@angular/router';
import { SessionService } from '../../shared/session.service';
import { Observable, of } from 'rxjs';
import { map, catchError, switchMap } from 'rxjs/operators';
import { ProjectService, ArtifactService } from "../../../lib/services";
import { CommonRoutes } from "../../../lib/entities/shared.const";
@Injectable()
export class ArtifactGuard implements CanActivate, CanActivateChild {
constructor(
private sessionService: SessionService,
private artifactService: ArtifactService,
private projectService: ProjectService,
private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
const projectId = route.params['id'];
const repoName = route.params['repo'];
const digest = route.params['digest'];
return this.projectService.getProject(projectId).pipe(
switchMap((project) => {
return this.hasArtifactPerm(project.name, repoName, digest);
}),
catchError(err => {
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
return of(false);
})
);
}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
return this.canActivate(route, state);
}
hasArtifactPerm(projectName: string, repoName: string, digest): Observable<boolean> {
// Note: current user will have the permission to visit the project when the user can get response from GET /projects/:id API.
return this.artifactService.getArtifactFromDigest(projectName, repoName, digest).pipe(
() => {
return of(true);
},
catchError(err => {
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
return of(false);
})
);
}
}
import { TestBed, inject } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { LeavingArtifactSummaryRouteDeactivate } from './leaving-artifact-summary-deactivate.service';
import { ConfirmationDialogService } from '../confirmation-dialog/confirmation-dialog.service';
import { of } from 'rxjs';
describe('LeavingArtifactSummaryRouteDeactivate', () => {
let fakeConfirmationDialogService = {
confirmationConfirm$: of({
state: 1,
source: 2
})
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
providers: [
LeavingArtifactSummaryRouteDeactivate,
{ provide: ConfirmationDialogService, useValue: fakeConfirmationDialogService }
]
});