Merge pull request #10790 from AllForNothing/routing-modify

Improve routing and UI for artifact pages
This commit is contained in:
Will Sun 2020-02-24 09:39:36 +08:00 committed by GitHub
commit ca871d0eb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
119 changed files with 1594 additions and 2353 deletions

View File

@ -13,11 +13,8 @@
// limitations under the License.
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CoreModule } from '../core/core.module';
import { SharedModule } from '../shared/shared.module';
import { RepositoryModule } from '../repository/repository.module';
import { PasswordSettingComponent } from './password-setting/password-setting.component';
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
import { SignUpComponent } from './sign-up/sign-up.component';
@ -33,7 +30,6 @@ import { AccountSettingsModalService } from './account-settings/account-settings
CoreModule,
RouterModule,
SharedModule,
RepositoryModule
],
declarations: [
PasswordSettingComponent,

View File

@ -14,13 +14,10 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { RouterModule } from '@angular/router';
import { ProjectModule } from '../project/project.module';
import { UserModule } from '../user/user.module';
import { AccountModule } from '../account/account.module';
import { RepositoryModule } from '../repository/repository.module';
import { GroupModule } from '../group/group.module';
import { NavigatorComponent } from './navigator/navigator.component';
import { GlobalSearchComponent } from './global-search/global-search.component';
import { FooterComponent } from './footer/footer.component';
@ -36,7 +33,6 @@ import { SearchTriggerService } from './global-search/search-trigger.service';
UserModule,
AccountModule,
RouterModule,
RepositoryModule,
GroupModule
],
declarations: [

View File

@ -13,40 +13,26 @@
// limitations under the License.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
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';
import { PageNotFoundComponent } from './shared/not-found/not-found.component';
import { HarborShellComponent } from './base/harbor-shell/harbor-shell.component';
import { ConfigurationComponent } from './config/config.component';
import { DevCenterComponent } from './dev-center/dev-center.component';
import { GcPageComponent } from './gc-page/gc-page.component';
import { VulnerabilityPageComponent } from './vulnerability-page/vulnerability-page.component';
import { UserComponent } from './user/user.component';
import { SignInComponent } from './sign-in/sign-in.component';
import { ResetPasswordComponent } from './account/password-setting/reset-password/reset-password.component';
import { GroupComponent } from './group/group.component';
import { TotalReplicationPageComponent } from './replication/total-replication/total-replication-page.component';
import { ReplicationTasksPageComponent } from './replication/replication-tasks-page/replication-tasks-page.component';
import { DestinationPageComponent } from './replication/destination/destination-page.component';
import { AuditLogComponent } from './log/audit-log.component';
import { LogPageComponent } from './log/log-page.component';
import { RepositoryPageComponent } from './repository/repository-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';
import { ProjectDetailComponent } from './project/project-detail/project-detail.component';
import { MemberComponent } from './project/member/member.component';
@ -61,7 +47,6 @@ import { HelmChartDetailComponent } from './project/helm-chart/helm-chart-detail
import { OidcOnboardComponent } from './oidc-onboard/oidc-onboard.component';
import { LicenseComponent } from './license/license.component';
import { SummaryComponent } from './project/summary/summary.component';
import { TagFeatureIntegrationComponent } from './project/tag-feature-integration/tag-feature-integration.component';
import { TagRetentionComponent } from './project/tag-feature-integration/tag-retention/tag-retention.component';
import { ImmutableTagComponent } from './project/tag-feature-integration/immutable-tag/immutable-tag.component';
@ -72,8 +57,9 @@ 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';
import { RepositoryGridviewComponent } from "./project/repository/repository-gridview.component";
import { ArtifactListPageComponent } from "./project/repository/artifact-list-page/artifact-list-page.component";
import { ArtifactSummaryComponent } from "./project/repository/artifact/artifact-summary.component";
const harborRoutes: Routes = [
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
@ -99,7 +85,6 @@ const harborRoutes: Routes = [
{
path: 'harbor',
component: HarborShellComponent,
// canActivate: [AuthCheckGuard],
canActivateChild: [AuthCheckGuard],
children: [
{ path: '', redirectTo: 'projects', pathMatch: 'full' },
@ -169,41 +154,6 @@ const harborRoutes: Routes = [
canActivate: [SystemAdminGuard],
canActivateChild: [SystemAdminGuard]
},
{
path: 'tags/:id/:repo',
component: ArtifactListPageComponent,
canActivate: [MemberGuard],
resolve: {
projectResolver: ProjectRoutingResolver
}
},
{
path: 'projects/:id/repositories/:repo',
component: ArtifactListPageComponent,
canActivate: [MemberGuard],
canDeactivate: [LeavingRepositoryRouteDeactivate],
resolve: {
projectResolver: ProjectRoutingResolver
},
},
{
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
}
},
{
path: 'projects/:id/helm-charts/:chart/versions',
component: ListChartVersionsComponent,
@ -248,7 +198,7 @@ const harborRoutes: Routes = [
action: USERSTATICPERMISSION.REPOSITORY.VALUE.LIST
}
},
component: RepositoryPageComponent,
component: RepositoryGridviewComponent
},
{
path: 'helm-charts',
@ -261,17 +211,6 @@ const harborRoutes: Routes = [
},
component: ListChartsComponent
},
{
path: 'repositories/:repo/tags',
canActivate: [MemberPermissionGuard],
data: {
permissionParam: {
resource: USERSTATICPERMISSION.REPOSITORY.KEY,
action: USERSTATICPERMISSION.REPOSITORY.VALUE.LIST
}
},
component: ArtifactListPageComponent
},
{
path: 'members',
canActivate: [MemberPermissionGuard],
@ -374,6 +313,38 @@ const harborRoutes: Routes = [
}
]
},
{
path: 'projects/:id/repositories/:repo',
component: ArtifactListPageComponent,
canActivate: [MemberGuard],
resolve: {
projectResolver: ProjectRoutingResolver
}
},
{
path: 'projects/:id/repositories/:repo/depth/:depth',
component: ArtifactListPageComponent,
canActivate: [MemberGuard],
resolve: {
projectResolver: ProjectRoutingResolver
},
},
{
path: 'projects/:id/repositories/:repo/artifacts/:digest',
component: ArtifactSummaryComponent,
canActivate: [MemberGuard],
resolve: {
projectResolver: ProjectRoutingResolver
}
},
{
path: 'projects/:id/repositories/:repo/depth/:depth/artifacts/:digest',
component: ArtifactSummaryComponent,
canActivate: [MemberGuard],
resolve: {
projectResolver: ProjectRoutingResolver
}
},
{
path: 'configs',
component: ConfigurationComponent,

View File

@ -4,8 +4,8 @@ import { fromEvent } from 'rxjs';
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';
import { Label } from "../../../../lib/services";
import { Artifact } from "../../../../../ng-swagger-gen/models/artifact";
@Component({
selector: "hbr-chart-version-label-filter",

View File

@ -12,31 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared/shared.module';
import { RepositoryModule } from '../repository/repository.module';
import { ReplicationModule } from '../replication/replication.module';
import { SummaryModule } from './summary/summary.module';
import { TagFeatureIntegrationModule } from './tag-feature-integration/tag-feature-integration.module';
import { LogModule } from '../log/log.module';
import { ProjectComponent } from './project.component';
import { CreateProjectComponent } from './create-project/create-project.component';
import { ListProjectComponent } from './list-project/list-project.component';
import { ProjectDetailComponent } from './project-detail/project-detail.component';
import { MemberComponent } from './member/member.component';
import { AddMemberComponent } from './member/add-member/add-member.component';
import { AddGroupComponent } from './member/add-group/add-group.component';
// import { ProjectService } from '@harbor/ui';
import { MemberService } from './member/member.service';
import { RobotService } from './robot-account/robot-account.service';
import { ProjectRoutingResolver } from './project-routing-resolver.service';
import { TargetExistsValidatorDirective } from '../shared/target-exists-directive';
import { ProjectLabelComponent } from "../project/project-label/project-label.component";
import { HelmChartModule } from './helm-chart/helm-chart.module';
import { RobotAccountComponent } from './robot-account/robot-account.component';
import { AddRobotComponent } from './robot-account/add-robot/add-robot.component';
@ -47,11 +39,34 @@ import { AddWebhookComponent } from './webhook/add-webhook/add-webhook.component
import { AddWebhookFormComponent } from './webhook/add-webhook-form/add-webhook-form.component';
import { ScannerComponent } from "./scanner/scanner.component";
import { ConfigScannerService } from "../config/scanner/config-scanner.service";
import { RepositoryGridviewComponent } from "./repository/repository-gridview.component";
import { ResultTipHistogramComponent } from "./repository/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component";
import { ResultGridComponent } from "./repository/vulnerability-scanning/result-grid.component";
import { ResultBarChartComponent } from "./repository/vulnerability-scanning/result-bar-chart.component";
import { HistogramChartComponent } from "./repository/vulnerability-scanning/histogram-chart/histogram-chart.component";
import { ResultTipComponent } from "./repository/vulnerability-scanning/result-tip.component";
import { ArtifactListPageComponent } from "./repository/artifact-list-page/artifact-list-page.component";
import { ProjectLabelComponent } from "./project-label/project-label.component";
import { ArtifactListComponent } from "./repository/artifact-list-page/artifact-list/artifact-list.component";
import { ArtifactTagComponent } from "./repository/artifact/artifact-tag/artifact-tag.component";
import { ArtifactCommonPropertiesComponent } from "./repository/artifact/artifact-common-properties/artifact-common-properties.component";
import { ArtifactAdditionsComponent } from "./repository/artifact/artifact-additions/artifact-additions.component";
import { ArtifactSummaryComponent } from "./repository/artifact/artifact-summary.component";
import { ArtifactListTabComponent } from "./repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component";
import { BuildHistoryComponent } from "./repository/artifact/artifact-additions/build-history/build-history.component";
import { DependenciesComponent } from "./repository/artifact/artifact-additions/dependencies/dependencies.component";
import { SummaryComponent } from "./repository/artifact/artifact-additions/summary/summary.component";
import { ValuesComponent } from "./repository/artifact/artifact-additions/values/values.component";
import {
ArtifactVulnerabilitiesComponent
} from "./repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component";
import { RepositoryDefaultService, RepositoryService } from "./repository/repository.service";
import { ArtifactDefaultService, ArtifactService } from "./repository/artifact/artifact.service";
import { GridViewComponent } from "./repository/gridview/grid-view.component";
@NgModule({
imports: [
SharedModule,
RepositoryModule,
ReplicationModule,
LogModule,
RouterModule,
@ -76,9 +91,38 @@ import { ConfigScannerService } from "../config/scanner/config-scanner.service";
AddWebhookComponent,
AddWebhookFormComponent,
ScannerComponent,
RepositoryGridviewComponent,
HistogramChartComponent,
ResultTipHistogramComponent,
ResultBarChartComponent,
ResultGridComponent,
ResultTipComponent,
ArtifactListPageComponent,
ArtifactListComponent,
ArtifactListTabComponent,
ArtifactSummaryComponent,
ArtifactCommonPropertiesComponent,
ArtifactTagComponent,
ArtifactAdditionsComponent,
BuildHistoryComponent,
DependenciesComponent,
SummaryComponent,
ValuesComponent,
ArtifactVulnerabilitiesComponent,
GridViewComponent,
],
exports: [ProjectComponent, ListProjectComponent],
providers: [ProjectRoutingResolver, MemberService, RobotService, WebhookService, ConfigScannerService]
providers: [
ProjectRoutingResolver,
MemberService,
RobotService,
WebhookService,
ConfigScannerService,
RepositoryDefaultService,
ArtifactDefaultService,
{ provide: RepositoryService, useClass: RepositoryDefaultService },
{ provide: ArtifactService, useClass: ArtifactDefaultService },
]
})
export class ProjectModule {

View File

@ -0,0 +1,14 @@
<div>
<div class="breadcrumb" *ngIf="!withAdmiral">
<a (click)="goProBack()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
<span class="back-icon"><</span>
<a (click)="watchGoBackEvt(projectId)">{{'REPOSITORY.REPOSITORIES'| translate}}</a>
<span *ngIf="depth">&lt;<a (click)="backInitRepo()">{{repoName}}</a></span>
<span *ngIf="referArtifactNameArray?.length>=1" >
<span *ngFor="let digest of referArtifactNameArray;let i = index">
&lt;<a (click)="jumpDigest(i)" >{{digest | slice:0:15}}</a></span>
</span>
</div>
<artifact-list [repoName]="repoName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole"
[projectId]="projectId" [memberRoleID]="projectMemberRoleId" [isGuest]="isGuest"></artifact-list>
</div>

View File

@ -5,14 +5,15 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { SessionService } from "../../../shared/session.service";
import { AppConfigService } from "../../../app-config.service";
import { ArtifactService } from "../../../../../ng-swagger-gen/services/artifact.service";
import { ArtifactDefaultService } from "../artifact/artifact.service";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../lib/entities/service.config";
import { AppConfigService } from '../../app-config.service';
import { SessionService } from '../../shared/session.service';
import { ArtifactService } from '../../../lib/services';
describe('ArtifactListPageComponent', () => {
let component: ArtifactListPageComponent;
let fixture: ComponentFixture<ArtifactListPageComponent>;
@ -42,7 +43,9 @@ describe('ArtifactListPageComponent', () => {
const mockActivatedRoute = {
RouterparamMap: of({ get: (key) => 'value' }),
snapshot: {
params: { id: 1 },
params: {
id: 1,
},
parent: {
params: { id: 1 },
@ -59,7 +62,15 @@ describe('ArtifactListPageComponent', () => {
ismember: true,
role_name: 'master',
}
})
}),
params: {
subscribe: () => {
return of(null);
}
}
};
const config: IServiceConfig = {
repositoryBaseEndpoint: "/api/repositories/testing"
};
beforeEach(async(() => {
TestBed.configureTestingModule({
@ -71,13 +82,14 @@ describe('ArtifactListPageComponent', () => {
ClarityModule,
TranslateModule.forRoot(),
FormsModule,
RouterTestingModule,
NoopAnimationsModule,
HttpClientTestingModule
],
declarations: [ArtifactListPageComponent],
providers: [
TranslateService,
ArtifactDefaultService,
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: SessionService, useValue: mockSessionService },
{ provide: AppConfigService, useValue: mockAppConfigService },
{ provide: Router, useValue: mockRouter },

View File

@ -13,12 +13,12 @@
// limitations under the License.
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AppConfigService } from '../../app-config.service';
import { SessionService } from '../../shared/session.service';
import { Project } from '../../project/project';
import { ArtifactListComponent } from "../../../lib/components/artifact-list/artifact-list.component";
import { ArtifactClickEvent, ArtifactService } from "../../../lib/services";
import { clone } from '../../../lib/utils/utils';
import { ArtifactListComponent } from "./artifact-list/artifact-list.component";
import { ArtifactDefaultService } from "../artifact/artifact.service";
import { AppConfigService } from "../../../app-config.service";
import { SessionService } from "../../../shared/session.service";
import { ArtifactClickEvent } from "../../../../lib/services";
import { Project } from "../../project";
@Component({
selector: 'artifact-list-page',
@ -37,13 +37,20 @@ export class ArtifactListPageComponent implements OnInit {
@ViewChild(ArtifactListComponent, {static: false})
repositoryComponent: ArtifactListComponent;
depth: string;
constructor(
private route: ActivatedRoute,
private router: Router,
private artifactService: ArtifactService,
private artifactService: ArtifactDefaultService,
private appConfigService: AppConfigService,
private session: SessionService) {
this.route.params.subscribe(params => {
this.depth = this.route.snapshot.params['depth'];
if (this.depth) {
const arr: string[] = this.depth.split('-');
this.referArtifactNameArray = arr.slice(0, arr.length - 1);
}
});
}
ngOnInit() {
@ -51,9 +58,7 @@ export class ArtifactListPageComponent implements OnInit {
if (!this.projectId) {
this.projectId = this.route.snapshot.parent.params['id'];
}
let resolverData = this.route.snapshot.data;
if (resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
this.isGuest = (<Project>resolverData['projectResolver']).current_user_role_id === 3;
@ -61,11 +66,6 @@ export class ArtifactListPageComponent implements OnInit {
}
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 {
@ -82,17 +82,6 @@ export class ArtifactListPageComponent implements OnInit {
hasChanges(): boolean {
return this.repositoryComponent.hasChanges();
}
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);
}
watchGoBackEvt(projectId: string| number): void {
this.router.navigate(["harbor", "projects", projectId, "repositories"]);
}
@ -100,25 +89,14 @@ export class ArtifactListPageComponent implements OnInit {
this.router.navigate(["harbor", "projects"]);
}
backInitRepo() {
this.referArtifactNameArray = [];
sessionStorage.removeItem('reference');
this.updateArtifactList('repoName');
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.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));
jumpDigest(index: number) {
const arr: string[] = this.referArtifactNameArray.slice(0, index + 1 );
if ( arr && arr.length) {
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repoName, "depth", arr.join('-')]);
} else {
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repoName]);
}
updateArtifactList(res): void {
this.artifactService.triggerUploadArtifact.next(res);
}
putArtifactReferenceArr(digestArray) {
this.referArtifactNameArray = digestArray;
}
}

View File

@ -67,7 +67,7 @@
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="clrLoad($event)" class="datagrid-top" [class.embeded-datagrid]="isEmbedded"
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="clrDgRefresh($event)" class="datagrid-top" [class.embeded-datagrid]="isEmbedded"
[(clrDgSelected)]="selectedRow">
<clr-dg-action-bar>
<button [clrLoading]="scanBtnState" type="button" class="btn btn-secondary scan-btn"
@ -146,13 +146,13 @@
<img class="artifact-icon"
[src]="artifact.type==='IMAGE'||artifact.type==='CHART'||artifact.type ==='CNAB'?'images/artifact-'+artifact.type.toLowerCase()+'.svg':'images/artifact-default.svg'" />
&nbsp; &nbsp;
<a href="javascript:void(0)" class="max-width-100" (click)="onTagClick(artifact)"
<a href="javascript:void(0)" class="max-width-100" (click)="goIntoArtifactSummaryPage(artifact)"
title="{{artifact.digest}}">
{{ artifact.digest | slice:0:15}}</a>
<clr-tooltip *ngIf="artifact.references">
<clr-tooltip *ngIf="artifact?.references && artifact?.references?.length">
<div clrTooltipTrigger class="level-border">
<div class="inner truncated ">
<a href="javascript:void(0)" (click)="refer(artifact)">
<a href="javascript:void(0)" (click)="goIntoIndexArtifact(artifact)">
<clr-icon class="icon-folder" shape="folder"></clr-icon>
</a>
</div>

View File

@ -1,4 +1,44 @@
@import "../../mixin";
@mixin text-overflow
{
overflow: hidden;
text-overflow: ellipsis;
word-wrap:break-word;
white-space: nowrap
}
@mixin text-overflow-param($width) {
width: $width;
@include text-overflow;
}
@mixin grid-right-top-pos{
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
}
@mixin absolute-center($width:108px) {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: $width !important;
height: $width !important;
}
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin dropdown-as-action-button {
margin: .25rem .5rem .25rem 0;
}
.option-right {
padding-right: 18px;
@ -350,3 +390,4 @@ clr-datagrid {
.eslip {
margin-left: -3px;
}

View File

@ -1,32 +1,37 @@
import { ComponentFixture, TestBed, async } from "@angular/core/testing";
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from "@angular/core";
import { SharedModule } from "../../utils/shared/shared.module";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ImageNameInputComponent } from "../image-name-input/image-name-input.component";
import { ArtifactListTabComponent } from "./artifact-list-tab.component";
import { ErrorHandler } from "../../utils/error-handler/error-handler";
import { Label, Tag } from "../../services/interface";
import { SERVICE_CONFIG, IServiceConfig } from "../../entities/service.config";
import {
ScanningResultService, ScanningResultDefaultService,
RetagService, RetagDefaultService, ProjectService, ProjectDefaultService, ArtifactService, ArtifactDefaultService
} from "../../services";
import { CopyInputComponent } from "../push-image/copy-input.component";
import { LabelPieceComponent } from "../label-piece/label-piece.component";
import { LabelDefaultService, LabelService } from "../../services/label.service";
import { UserPermissionService, UserPermissionDefaultService } from "../../services/permission.service";
import { USERSTATICPERMISSION } from "../../services/permission-static";
import { OperationService } from "../operation/operation.service";
import { of } from "rxjs";
import { delay } from "rxjs/operators";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { HttpClientTestingModule } from "@angular/common/http/testing";
import { HttpClient } from "@angular/common/http";
import { ChannelService } from "../../services/channel.service";
import { Artifact, Reference } from "./artifact";
import { ActivatedRoute } from "@angular/router";
import { ActivatedRoute, Router } from "@angular/router";
import { ArtifactDefaultService, ArtifactService } from "../../../artifact/artifact.service";
import {
Label,
LabelDefaultService,
LabelService,
ProjectDefaultService,
ProjectService,
RetagDefaultService,
RetagService,
ScanningResultDefaultService,
ScanningResultService,
UserPermissionDefaultService,
UserPermissionService,
USERSTATICPERMISSION
} from "../../../../../../lib/services";
import { Artifact, Reference } from "../../../artifact/artifact";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../../../lib/entities/service.config";
import { SharedModule } from "../../../../../../lib/utils/shared/shared.module";
import { LabelPieceComponent } from "../../../../../../lib/components/label-piece/label-piece.component";
import { ConfirmationDialogComponent } from "../../../../../../lib/components/confirmation-dialog";
import { ImageNameInputComponent } from "../../../../../../lib/components/image-name-input/image-name-input.component";
import { CopyInputComponent } from "../../../../../../lib/components/push-image/copy-input.component";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
import { ChannelService } from "../../../../../../lib/services/channel.service";
import { OperationService } from "../../../../../../lib/components/operation/operation.service";
describe("ArtifactListTabComponent (inline template)", () => {
@ -43,13 +48,35 @@ describe("ArtifactListTabComponent (inline template)", () => {
name: "Clair"
};
let mockActivatedRoute = {
snapshot: {
params: {
id: 1,
repo: "test",
digest: "ABC",
subscribe: () => {
return of(null);
}
},
data: {
projectResolver: {
has_project_admin_role: true,
current_user_role_id: 3,
name: "demo"
}
}
},
data: of(
{
projectResolver: {
name: 'library'
}
}
)
),
params: {
subscribe: () => {
return of(null);
}
}
};
let mockArtifacts: Artifact[] = [
{
@ -68,7 +95,8 @@ describe("ArtifactListTabComponent (inline template)", () => {
artifact_id: 2,
pull_time: '2020-01-06T09:40:08.036866579Z',
push_time: '2020-01-06T09:40:08.036866579Z',
},],
}
],
references: [new Reference(1), new Reference(2)],
media_type: 'string',
"digest": "sha256:4875cda368906fd670c9629b5e416ab3d6c0292015f3c3f12ef37dc9a32fc8d4",
@ -178,12 +206,15 @@ describe("ArtifactListTabComponent (inline template)", () => {
{resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE},
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE},
];
const mockRouter = {
navigate: () => { }
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
BrowserAnimationsModule,
HttpClientTestingModule
HttpClientTestingModule,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
@ -198,6 +229,8 @@ describe("ArtifactListTabComponent (inline template)", () => {
providers: [
ErrorHandler,
ChannelService,
ArtifactDefaultService,
{ provide: Router, useValue: mockRouter },
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: ArtifactService, useClass: ArtifactDefaultService },
{ provide: ProjectService, useClass: ProjectDefaultService },
@ -215,7 +248,6 @@ describe("ArtifactListTabComponent (inline template)", () => {
beforeEach(() => {
fixture = TestBed.createComponent(ArtifactListTabComponent);
comp = fixture.componentInstance;
comp.projectId = 1;
comp.repoName = "library/nginx";
comp.hasDeleteImagePermission = true;
@ -224,11 +256,7 @@ describe("ArtifactListTabComponent (inline template)", () => {
comp.registryUrl = "http://registry.testing.com";
comp.withNotary = false;
comp.withAdmiral = false;
let labelService: LabelService;
artifactService = fixture.debugElement.injector.get(ArtifactService);
spy = spyOn(artifactService, "getArtifactList").and.returnValues(of(
{
@ -243,36 +271,14 @@ describe("ArtifactListTabComponent (inline template)", () => {
.withArgs(comp.projectId, permissions )
.and.returnValue(of([mockHasAddLabelImagePermission, mockHasRetagImagePermission,
mockHasDeleteImagePermission, mockHasScanImagePermission]));
labelService = fixture.debugElement.injector.get(LabelService);
spyLabels = spyOn(labelService, "getGLabels").and.returnValues(of(mockLabels).pipe(delay(0)));
spyLabels1 = spyOn(labelService, "getPLabels").withArgs(comp.projectId).and.returnValues(of(mockLabels1).pipe(delay(0)));
fixture.detectChanges();
});
it("should load data", async(() => {
expect(spy.calls.any).toBeTruthy();
}));
it("should load project scanner", async(() => {
expect(spyScanner.calls.count()).toEqual(2);
}));
it("should load and render data", () => {
;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(del => del.classes["datagrid-cell"]);
fixture.detectChanges();
expect(de).toBeTruthy();
let el: HTMLElement = de.nativeElement;
expect(el).toBeTruthy();
expect(el.textContent.trim()).toEqual("sha256:4875cda3");
});
});
});

View File

@ -13,58 +13,56 @@
// limitations under the License.
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
Input, OnDestroy,
OnInit,
Output,
ViewChild,
} from "@angular/core";
import { forkJoin, Observable, Subject, throwError as observableThrowError, of } from "rxjs";
import { forkJoin, Observable, Subject, of, Subscription } from "rxjs";
import { catchError, debounceTime, distinctUntilChanged, finalize, map } from 'rxjs/operators';
import { TranslateService } from "@ngx-translate/core";
import { Comparator, Label, State, Tag, ArtifactClickEvent, VulnerabilitySummary } from "../../services/interface";
import { ClrLoadingState, ClrDatagridStateInterface, ClrDatagridComparatorInterface } from "@clr/angular";
import { HttpParams } from "@angular/common/http";
import { ActivatedRoute, Router } from "@angular/router";
import {
RequestQueryParams,
RetagService,
ScanningResultService,
ProjectService,
ArtifactService
} from "../../services";
import { ErrorHandler } from "../../utils/error-handler/error-handler";
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../entities/shared.const";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
ArtifactClickEvent,
Comparator, Label, LabelService, ProjectService,
RetagService, ScanningResultService,
State, Tag,
UserPermissionService, USERSTATICPERMISSION, VulnerabilitySummary
} from "../../../../../../lib/services";
import {
calculatePage,
clone,
CustomComparator,
DEFAULT_PAGE_SIZE, DEFAULT_SUPPORTED_MIME_TYPE,
doFiltering,
doSorting, formatSize,
VULNERABILITY_SCAN_STATUS,
} from "../../utils/utils";
import { CopyInputComponent } from "../push-image/copy-input.component";
import { LabelService } from "../../services/label.service";
import { UserPermissionService } from "../../services/permission.service";
import { USERSTATICPERMISSION } from "../../services/permission-static";
import { operateChanges, OperateInfo, OperationState } from "../operation/operate";
import { OperationService } from "../operation/operation.service";
import { ImageNameInputComponent } from "../image-name-input/image-name-input.component";
import { errorHandler as errorHandFn } from "../../utils/shared/shared.utils";
import { ClrLoadingState, ClrDatagridStateInterface, ClrDatagridComparatorInterface } from "@clr/angular";
import { ChannelService } from "../../services/channel.service";
import { Artifact, Reference } from "./artifact";
import { HttpParams } from "@angular/common/http";
import { ActivatedRoute } from "@angular/router";
formatSize, VULNERABILITY_SCAN_STATUS
} from "../../../../../../lib/utils/utils";
import {
ConfirmationAcknowledgement,
ConfirmationDialogComponent,
ConfirmationMessage
} from "../../../../../../lib/components/confirmation-dialog";
import { ImageNameInputComponent } from "../../../../../../lib/components/image-name-input/image-name-input.component";
import { CopyInputComponent } from "../../../../../../lib/components/push-image/copy-input.component";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
import { ArtifactDefaultService } from "../../../artifact/artifact.service";
import { OperationService } from "../../../../../../lib/components/operation/operation.service";
import { ChannelService } from "../../../../../../lib/services/channel.service";
import {
ConfirmationButtons,
ConfirmationState,
ConfirmationTargets
} from "../../../../../../lib/entities/shared.const";
import { operateChanges, OperateInfo, OperationState } from "../../../../../../lib/components/operation/operate";
import { errorHandler } from "../../../../../../lib/utils/shared/shared.utils";
import { Artifact } from "../../../artifact/artifact";
import { Project } from "../../../../project";
export interface LabelState {
iconsShow: boolean;
@ -77,7 +75,7 @@ export const AVAILABLE_TIME = '0001-01-01T00:00:00.000Z';
templateUrl: './artifact-list-tab.component.html',
styleUrls: ['./artifact-list-tab.component.scss']
})
export class ArtifactListTabComponent implements OnInit, AfterViewInit {
export class ArtifactListTabComponent implements OnInit, OnDestroy {
signedCon: { [key: string]: any | string[] } = {};
@Input() projectId: number;
@ -86,18 +84,11 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
@Input() repoName: string;
referArtifactArray: string[] = [];
@Input() isEmbedded: boolean;
@Input() hasSignedIn: boolean;
@Input() isGuest: boolean;
@Input() registryUrl: string;
@Input() withNotary: boolean;
@Input() withAdmiral: boolean;
@Output() refreshRepo = new EventEmitter<boolean>();
@Output() tagClickEvent = new EventEmitter<ArtifactClickEvent>();
@Output() signatureOutput = new EventEmitter<any>();
@Output() putReferArtifactArray = new EventEmitter<string[]>();
tags: Tag[];
artifactList: Artifact[] = [];
availableTime = AVAILABLE_TIME;
@ -163,52 +154,93 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
onSendingScanCommand: boolean;
artifactDigest: string;
depth: string;
hasInit: boolean = false;
triggerSub: Subscription;
labelNameFilterSub: Subscription;
stickLabelNameFilterSub: Subscription;
constructor(
private errorHandler: ErrorHandler,
private errorHandlerService: ErrorHandler,
private retagService: RetagService,
private userPermissionService: UserPermissionService,
private labelService: LabelService,
private artifactService: ArtifactService,
private artifactService: ArtifactDefaultService,
private translateService: TranslateService,
private ref: ChangeDetectorRef,
private operationService: OperationService,
private channel: ChannelService,
private projectService: ProjectService,
private activatedRoute: ActivatedRoute,
private scanningService: ScanningResultService
) { }
private scanningService: ScanningResultService,
private router: Router,
) {
}
ngOnInit() {
this.activatedRoute.params.subscribe(params => {
this.depth = this.activatedRoute.snapshot.params['depth'];
if (this.depth) {
const arr: string[] = this.depth.split('-');
this.artifactDigest = this.depth.split('-')[arr.length - 1];
}
if (this.hasInit) {
this.currentPage = 1;
this.totalCount = 0;
const st: ClrDatagridStateInterface = {page: {from: 0, to: this.pageSize - 1, size: this.pageSize}};
this.clrLoad(st);
}
this.init();
});
}
ngOnDestroy() {
if (this.triggerSub) {
this.triggerSub.unsubscribe();
this.triggerSub = null;
}
if (this.labelNameFilterSub) {
this.labelNameFilterSub.unsubscribe();
this.labelNameFilterSub = null;
}
if (this.stickLabelNameFilterSub) {
this.stickLabelNameFilterSub.unsubscribe();
this.stickLabelNameFilterSub = null;
}
}
init() {
this.hasInit = true;
this.depth = this.activatedRoute.snapshot.params['depth'];
if (this.depth) {
const arr: string[] = this.depth.split('-');
this.artifactDigest = this.depth.split('-')[arr.length - 1];
}
if (!this.projectId) {
this.errorHandler.error("Project ID cannot be unset.");
this.errorHandlerService.error("Project ID cannot be unset.");
return;
}
this.activatedRoute.data.subscribe(res => {
this.projectName = res.projectResolver.name;
});
const resolverData = this.activatedRoute.snapshot.data;
if (resolverData) {
const pro: Project = <Project>resolverData['projectResolver'];
this.projectName = pro.name;
}
this.getProjectScanner();
if (!this.repoName) {
this.errorHandler.error("Repo name cannot be unset.");
this.errorHandlerService.error("Repo name cannot be unset.");
return;
}
let refer = JSON.parse(sessionStorage.getItem('reference'));
if (refer && refer.projectId === this.projectId && refer.repo === this.repoName) {
this.referArtifactArray = refer.referArray;
}
this.artifactService.TriggerArtifactChan$.subscribe(res => {
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.labelNameFilter
if (!this.labelNameFilterSub) {
this.labelNameFilterSub = this.labelNameFilter
.pipe(debounceTime(500))
.pipe(distinctUntilChanged())
.subscribe((name: string) => {
if (this.filterName.length) {
this.filterOnGoing = true;
this.imageFilterLabels.forEach(data => {
if (data.label.name.indexOf(this.filterName) !== -1) {
data.show = true;
@ -216,19 +248,16 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
data.show = false;
}
});
setTimeout(() => {
setInterval(() => this.ref.markForCheck(), 200);
}, 1000);
}
});
this.stickLabelNameFilter
}
if (!this.stickLabelNameFilterSub) {
this.stickLabelNameFilterSub = this.stickLabelNameFilter
.pipe(debounceTime(500))
.pipe(distinctUntilChanged())
.subscribe((name: string) => {
if (this.stickName.length) {
this.filterOnGoing = true;
this.imageStickLabels.forEach(data => {
if (data.label.name.indexOf(this.stickName) !== -1) {
data.show = true;
@ -236,16 +265,10 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
data.show = false;
}
});
setTimeout(() => {
setInterval(() => this.ref.markForCheck(), 200);
}, 1000);
}
});
this.getImagePermissionRule(this.projectId);
}
ngAfterViewInit() {
this.getImagePermissionRule(this.projectId);
}
public get filterLabelPieceWidth() {
@ -292,8 +315,15 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
this.clrLoad(st);
}
// todo
clrDgRefresh(state: ClrDatagridStateInterface) {
setTimeout(() => {
this.clrLoad(state);
});
}
clrLoad(state: ClrDatagridStateInterface): void {
this.artifactList = [];
this.loading = true;
if (!state || !state.page) {
return;
}
@ -308,7 +338,6 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
sortBy = sortBy.fieldName ? sortBy.fieldName : sortBy;
sortBy = state.sort.reverse ? `-${sortBy}` : sortBy;
}
this.loading = true;
this.currentState = state;
// Pagination
@ -317,7 +346,6 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
params = params.set('with_scan_overview', 'true');
params = params.set('with_signature', 'true');
params = params.set('with_immutable_status', 'true');
if (pageNumber && this.pageSize) {
params = params.set('page', pageNumber + '').set('page_size', this.pageSize + '');
}
@ -329,37 +357,32 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
params = params.set(item.property, item.value);
});
}
this.projectService.getProject(this.projectId).subscribe(project => {
this.projectName = project.name;
let refer = JSON.parse(sessionStorage.getItem('reference'));
this.referArtifactArray = [];
if (refer && refer.projectId === this.projectId && refer.repo === this.repoName) {
this.referArtifactArray = refer.referArray;
}
if (this.referArtifactArray.length) {
if (this.artifactDigest) {
this.artifactService.getArtifactFromDigest(this.projectName, this.repoName, this.artifactDigest).subscribe(
res => {
let observableLists: Observable<Artifact>[] = [];
this.artifactService.getArtifactFromDigest(this.projectName, this.repoName,
this.referArtifactArray[this.referArtifactArray.length - 1]).subscribe(artifact => {
this.totalCount = artifact.references.length;
artifact.references.forEach((child, index) => {
this.totalCount = res.references.length;
res.references.forEach((child, index) => {
if (index >= (pageNumber - 1) * this.pageSize && index < pageNumber * this.pageSize) {
observableLists.push(this.artifactService.getArtifactFromDigest(this.projectName, this.repoName,
child.child_digest));
}
});
forkJoin(observableLists).pipe(finalize(() => {
this.loading = false;
})).subscribe(artifacts => {
this.artifactList = artifacts;
}, error => {
this.errorHandler.error(error);
});
this.errorHandlerService.error(error);
});
}, error => {
this.loading = false;
}
);
} else {
this.artifactService.getArtifactList(this.projectName, this.repoName, params).subscribe(res => {
this.artifactService.getArtifactList(this.projectName, this.repoName, params)
.pipe(finalize(() => this.loading = false))
.subscribe(res => {
if (res.headers) {
let xHeader: string = res.headers.get("X-Total-Count");
if (xHeader) {
@ -367,13 +390,11 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
}
}
this.artifactList = res.body;
this.loading = false;
}, error => {
// error
this.loading = false;
this.errorHandlerService.error(error);
});
}
});
}
refresh() {
@ -389,7 +410,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
});
this.imageFilterLabels = clone(this.imageLabels);
this.imageStickLabels = clone(this.imageLabels);
}, error => this.errorHandler.error(error));
}, error => this.errorHandlerService.error(error));
}
labelSelectedChange(artifact?: Artifact[]): void {
@ -453,7 +474,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
this.inprogress = false;
}, err => {
this.inprogress = false;
this.errorHandler.error(err);
this.errorHandlerService.error(err);
});
}
}
@ -472,7 +493,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
this.inprogress = false;
}, err => {
this.inprogress = false;
this.errorHandler.error(err);
this.errorHandlerService.error(err);
});
}
}
@ -607,48 +628,6 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
}
});
}
loadArtifactList(params?) {
this.artifactList = [];
this.loading = true;
let refer = JSON.parse(sessionStorage.getItem('reference'));
if (refer && refer.projectId === this.projectId && refer.repo === this.repoName) {
this.referArtifactArray = refer.referArray;
}
if (this.referArtifactArray.length) {
let observableLists: Observable<Artifact>[] = [];
this.artifactService.getArtifactFromDigest(this.projectName, this.repoName,
this.referArtifactArray[this.referArtifactArray.length - 1]).subscribe(artifact => {
artifact.references.forEach(child => {
observableLists.push(this.artifactService.getArtifactFromDigest(this.projectName, this.repoName,
child.child_digest));
});
forkJoin(observableLists).subscribe(artifacts => {
this.loading = false;
this.artifactList = artifacts;
});
});
} else {
this.artifactService.getArtifactList(this.projectName, this.repoName, params).subscribe(res => {
if (res.headers) {
let xHeader: string = res.headers.get("X-Total-Count");
if (xHeader) {
this.totalCount = parseInt(xHeader, 0);
}
}
this.artifactList = res.body;
this.loading = false;
}, error => {
// error
this.loading = false;
});
}
}
sizeTransform(tagSize: string): string {
return formatSize(tagSize);
}
@ -658,7 +637,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
this.retagDialogOpened = true;
this.retagSrcImage = this.repoName + ":" + this.selectedRow[0].digest;
} else {
this.errorHandler.error("One tag should be selected before retag.");
this.errorHandlerService.error("One tag should be selected before retag.");
}
}
@ -676,7 +655,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
}))
.subscribe(response => {
this.translateService.get('RETAG.MSG_SUCCESS').subscribe((res: string) => {
this.errorHandler.info(res);
this.errorHandlerService.info(res);
if (`${this.imageNameInput.projectName.value}/${this.imageNameInput.repoName.value}` === this.repoName) {
let st: State = this.currentState;
if (!st) {
@ -689,7 +668,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
}
});
}, error => {
this.errorHandler.error(error);
this.errorHandlerService.error(error);
});
}
@ -784,7 +763,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
operateChanges(operMessage, OperationState.success);
});
}), catchError(error => {
const message = errorHandFn(error);
const message = errorHandler(error);
this.translateService.get(message).subscribe(res =>
operateChanges(operMessage, OperationState.failure, res)
);
@ -802,16 +781,9 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
}
}
onTagClick(artifact: Artifact): void {
if (artifact) {
let evt: ArtifactClickEvent = {
project_id: this.projectId,
repository_name: this.repoName,
digest: artifact.digest,
artifact_id: artifact.id,
};
this.tagClickEvent.emit(evt);
}
goIntoArtifactSummaryPage(artifact: Artifact): void {
const relativeRouterLink: string[] = ['artifacts', artifact.digest];
this.router.navigate(relativeRouterLink , { relativeTo: this.activatedRoute });
}
onSuccess($event: any): void {
@ -832,7 +804,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
// Get vulnerability scanning status
scanStatus(artifact: Artifact): string {
if (artifact) {
let so = this.handleScanOverview(artifact.scan_overview);
let so = this.handleScanOverview((<any>artifact).scan_overview);
if (so && so.scan_status) {
return so.scan_status;
}
@ -864,7 +836,7 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
this.getAllLabels();
}
}
}, error => this.errorHandler.error(error));
}, error => this.errorHandlerService.error(error));
}
// Trigger scan
scanNow(): void {
@ -903,22 +875,15 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
}
return null;
}
goIntoIndexArtifact(artifact: Artifact) {
let depth: string = '';
if (this.depth) {
depth = this.depth + '-' + artifact.digest;
} else {
depth = artifact.digest;
}
const linkUrl = ['harbor', 'projects', this.projectId, 'repositories', this.repoName, 'depth', depth];
this.router.navigate(linkUrl);
}
refer(artifact: Artifact) {
this.referArtifactArray.push(artifact.digest);
sessionStorage.setItem('reference', JSON.stringify({ projectId: this.projectId, repo: this.repoName
, referArray: this.referArtifactArray}));
if (this.referArtifactArray.length) {
this.putReferArtifactArray.emit(this.referArtifactArray);
}
let st: ClrDatagridStateInterface = this.currentState;
if (!st) {
st = { page: {} };
}
st.page.size = this.pageSize;
st.page.from = 0;
st.page.to = this.pageSize - 1;
this.clrLoad(st);
}
}

View File

@ -4,7 +4,7 @@
<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">{{showCurrentTitle | slice:0:15}}</h2>
<h2 sub-header-title class="custom-h2">{{artifactDigest ? artifactDigest : showCurrentTitle | slice:0:15}}</h2>
</div>
</div>
</section>
@ -13,7 +13,7 @@
<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">
<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>
@ -54,9 +54,9 @@
</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" (tagClickEvent)="watchTagClickEvt($event)" (signatureOutput)="saveSignatures($event)"
<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" (putReferArtifactArray)="putReferArtifactArray($event)"></artifact-list-tab>
[isGuest]="isGuest" [projectId]="projectId" [memberRoleID]="memberRoleID"></artifact-list-tab>
</div>
</section>
</div>

View File

@ -0,0 +1,152 @@
import { ComponentFixture, TestBed, async, } from '@angular/core/testing';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { ArtifactListComponent } from './artifact-list.component';
import { of } from "rxjs";
import { delay } from 'rxjs/operators';
import { ClarityModule } from '@clr/angular';
import { ActivatedRoute } from '@angular/router';
import { RepositoryDefaultService, RepositoryService } from "../../repository.service";
import {
Repository,
RepositoryItem,
SystemInfo, SystemInfoDefaultService,
SystemInfoService,
} from "../../../../../lib/services";
import { ArtifactDefaultService, ArtifactService } from "../../artifact/artifact.service";
import { ChannelService } from "../../../../../lib/services/channel.service";
import { FormsModule } from "@angular/forms";
import { MarkdownModule } from "ngx-markdown";
import { TranslateFakeLoader, TranslateLoader, TranslateModule, TranslateService } from "@ngx-translate/core";
import { ErrorHandler } from "../../../../../lib/utils/error-handler";
import { HttpClientTestingModule } from "@angular/common/http/testing";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../../lib/entities/service.config";
import { SharedModule } from "../../../../../lib/utils/shared/shared.module";
describe('ArtifactListComponent (inline template)', () => {
let compRepo: ArtifactListComponent;
let fixture: ComponentFixture<ArtifactListComponent>;
let repositoryService: RepositoryService;
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);
}
}
};
let mockChannelService = {
scanCommand$: of(1)
};
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 mockRepoData: RepositoryItem[] = [
{
'id': 1,
'name': 'library/busybox',
'project_id': 1,
'description': 'asdfsadf',
'pull_count': 0,
'star_count': 0,
'tags_count': 1
},
{
'id': 2,
'name': 'library/nginx',
'project_id': 1,
'description': 'asdf',
'pull_count': 0,
'star_count': 0,
'tags_count': 1
}
];
let mockRepo: Repository = {
metadata: { xTotalCount: 2 },
data: mockRepoData
};
const fakedErrorHandler = {
error: () => {}
};
const config: IServiceConfig = {
repositoryBaseEndpoint: "/api/repositories/testing"
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ClarityModule,
SharedModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateFakeLoader,
}
})
],
schemas: [
NO_ERRORS_SCHEMA
],
declarations: [
ArtifactListComponent
],
providers: [
TranslateService,
{ provide: ErrorHandler, useValue: fakedErrorHandler },
{ provide: RepositoryService, useClass: RepositoryDefaultService },
{ provide: ChannelService, useValue: mockChannelService },
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
{ provide: ArtifactService, useClass: ArtifactDefaultService },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: SERVICE_CONFIG, useValue: config },
]
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(ArtifactListComponent);
compRepo = fixture.componentInstance;
compRepo.projectId = 1;
compRepo.hasProjectAdminRole = true;
compRepo.repoName = 'library/nginx';
repositoryService = fixture.debugElement.injector.get(RepositoryService);
systemInfoService = fixture.debugElement.injector.get(SystemInfoService);
artifactService = fixture.debugElement.injector.get(ArtifactService);
spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(of(mockRepo).pipe(delay(0)));
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();
});
});

View File

@ -13,16 +13,18 @@
// limitations under the License.
import { Component, OnInit, ViewChild, Input, Output, EventEmitter, OnDestroy } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { State } from '../../services/interface';
import { RepositoryService } from '../../services/repository.service';
import { ArtifactClickEvent, RepositoryItem, State, SystemInfo, SystemInfoService } from "../../../../../lib/services";
import {
RepositoryItem, ArtifactClickEvent,
SystemInfo, SystemInfoService, ArtifactService
} from '../../services';
import { ErrorHandler } from '../../utils/error-handler';
import { ConfirmationState, ConfirmationTargets } from '../../entities/shared.const';
import { ConfirmationDialogComponent, ConfirmationMessage, ConfirmationAcknowledgement } from '../confirmation-dialog';
ConfirmationAcknowledgement,
ConfirmationDialogComponent,
ConfirmationMessage
} from "../../../../../lib/components/confirmation-dialog";
import { ErrorHandler } from "../../../../../lib/utils/error-handler";
import { RepositoryService } from "../../repository.service";
import { ArtifactService } from "../../artifact/artifact.service";
import { ConfirmationState, ConfirmationTargets } from "../../../../../lib/entities/shared.const";
import { ActivatedRoute } from "@angular/router";
const TabLinkContentMap: { [index: string]: string } = {
'repo-info': 'info',
'repo-image': 'image'
@ -33,7 +35,7 @@ const TabLinkContentMap: { [index: string]: string } = {
templateUrl: './artifact-list.component.html',
styleUrls: ['./artifact-list.component.scss']
})
export class ArtifactListComponent implements OnInit, OnDestroy {
export class ArtifactListComponent implements OnInit {
signedCon: { [key: string]: any | string[] } = {};
@Input() projectId: number;
@Input() memberRoleID: number;
@ -60,14 +62,23 @@ export class ArtifactListComponent implements OnInit, OnDestroy {
@ViewChild('confirmationDialog', { static: false })
confirmationDlg: ConfirmationDialogComponent;
showCurrentTitle: string;
artifactDigest: string;
constructor(
private errorHandler: ErrorHandler,
private repositoryService: RepositoryService,
private systemInfoService: SystemInfoService,
private artifactService: ArtifactService,
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 : '';
@ -95,11 +106,6 @@ export class ArtifactListComponent implements OnInit, OnDestroy {
this.showCurrentTitle = res[res.length - 1];
}
});
let refer = JSON.parse(sessionStorage.getItem('reference'));
if (refer && refer.projectId === this.projectId && refer.repo === this.repoName) {
this.putReferArtifactArray(refer.referArray);
}
}
retrieve(state?: State) {
@ -113,19 +119,9 @@ export class ArtifactListComponent implements OnInit, OnDestroy {
this.systemInfoService.getSystemInfo()
.subscribe(systemInfo => this.systemInfo = systemInfo, error => this.errorHandler.error(error));
}
saveSignatures(event: { [key: string]: string[] }): void {
Object.assign(this.signedCon, event);
}
refresh() {
this.retrieve();
}
watchTagClickEvt(tagClickEvt: ArtifactClickEvent): void {
this.tagClickEvent.emit(tagClickEvt);
}
isCurrentTabLink(tabID: string): boolean {
return this.currentTabID === tabID;
}
@ -195,15 +191,4 @@ export class ArtifactListComponent implements OnInit, OnDestroy {
this.reset();
}
}
ngOnDestroy(): void {
sessionStorage.removeItem('reference');
}
putReferArtifactArray(referArtifactArray) {
if (referArtifactArray.length) {
this.showCurrentTitle = referArtifactArray[referArtifactArray.length - 1];
this.putArtifactReferenceArr.emit(referArtifactArray);
}
}
}

View File

@ -1,9 +1,9 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtifactAdditionsComponent } from './artifact-additions.component';
import { AdditionLinks } from "../../../../../ng-swagger-gen/models/addition-links";
import { HarborLibraryModule } from "../../../harbor-library.module";
import { IServiceConfig, SERVICE_CONFIG } from "../../../entities/service.config";
import { AdditionLinks } from "../../../../../../ng-swagger-gen/models/addition-links";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../../lib/entities/service.config";
import { ProjectModule } from "../../../project.module";
describe('ArtifactAdditionsComponent', () => {
const mockedAdditionLinks: AdditionLinks = {
@ -21,7 +21,7 @@ describe('ArtifactAdditionsComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HarborLibraryModule
ProjectModule
],
providers: [
{ provide: SERVICE_CONFIG, useValue: config },

View File

@ -1,7 +1,7 @@
import { Component, OnInit, Input, SimpleChanges } from '@angular/core';
import { AdditionLinks } from "../../../../../ng-swagger-gen/models/addition-links";
import { Component, OnInit, Input } from '@angular/core';
import { ADDITIONS } from "./models";
import { AdditionLink } from "../../../../../ng-swagger-gen/models/addition-link";
import { AdditionLinks } from "../../../../../../ng-swagger-gen/models/addition-links";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
@Component({
selector: 'artifact-additions',

View File

@ -3,13 +3,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtifactVulnerabilitiesComponent } from './artifact-vulnerabilities.component';
import { NO_ERRORS_SCHEMA } from "@angular/core";
import { ClarityModule } from "@clr/angular";
import { ErrorHandler } from "../../../../utils/error-handler";
import { AdditionsService } from "../additions.service";
import { VulnerabilityItem } from "../../../../services";
import { of } from "rxjs";
import { TranslateFakeLoader, TranslateLoader, TranslateModule } from "@ngx-translate/core";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { VulnerabilityItem } from "../../../../../../lib/services";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
describe('ArtifactVulnerabilitiesComponent', () => {
let component: ArtifactVulnerabilitiesComponent;

View File

@ -1,13 +1,11 @@
import { Component, Input, OnInit } from '@angular/core';
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { ErrorHandler } from "../../../../utils/error-handler";
import { AdditionsService } from "../additions.service";
import {
VulnerabilityItem
} from "../../../../services";
import { ClrDatagridComparatorInterface, ClrLoadingState } from "@clr/angular";
import { SEVERITY_LEVEL_MAP, VULNERABILITY_SEVERITY } from "../../../../utils/utils";
import { finalize } from "rxjs/operators";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { VulnerabilityItem } from "../../../../../../lib/services";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
import { SEVERITY_LEVEL_MAP, VULNERABILITY_SEVERITY } from "../../../../../../lib/utils/utils";
@Component({
selector: 'hbr-artifact-vulnerabilities',

View File

@ -1,14 +1,14 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from "@angular/core";
import { ClarityModule } from "@clr/angular";
import { ErrorHandler } from "../../../../utils/error-handler";
import { AdditionsService } from "../additions.service";
import { of } from "rxjs";
import { TranslateFakeLoader, TranslateLoader, TranslateModule } from "@ngx-translate/core";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { BuildHistoryComponent } from "./build-history.component";
import { ArtifactBuildHistory } from "../models";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
describe('BuildHistoryComponent', () => {
let component: BuildHistoryComponent;

View File

@ -1,9 +1,9 @@
import { Component, Input, OnInit } from "@angular/core";
import { ErrorHandler } from "../../../../utils/error-handler";
import { AdditionsService } from "../additions.service";
import { ArtifactBuildHistory } from "../models";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { finalize } from "rxjs/operators";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
@Component({
selector: "hbr-artifact-build-history",

View File

@ -1,12 +1,13 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { DependenciesComponent } from "./dependencies.component";
import { ErrorHandler } from '../../../../utils/error-handler';
import { AdditionsService } from '../additions.service';
import { of } from 'rxjs';
import { SERVICE_CONFIG, IServiceConfig } from '../../../../entities/service.config';
import { ArtifactDependency } from "../models";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../../../lib/entities/service.config";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
describe('DependenciesComponent', () => {
let component: DependenciesComponent;

View File

@ -4,9 +4,10 @@ import {
Input,
} from "@angular/core";
import { ArtifactDependency } from "../models";
import { ErrorHandler } from "../../../../utils/error-handler";
import { AdditionsService } from "../additions.service";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
@Component({
selector: "hbr-artifact-dependencies",

View File

@ -1,4 +1,4 @@
import { HelmChartMaintainer } from "../../../../app/project/helm-chart/helm-chart.interface.service";
import { HelmChartMaintainer } from "../../../helm-chart/helm-chart.interface.service";
export class ArtifactBuildHistory {
created: Date;

View File

@ -1,12 +1,12 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from "@angular/core";
import { ErrorHandler } from "../../../../utils/error-handler";
import { AdditionsService } from "../additions.service";
import { of } from "rxjs";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { SummaryComponent } from "./summary.component";
import { HarborLibraryModule } from "../../../../harbor-library.module";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../entities/service.config";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../../../lib/entities/service.config";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
import { ProjectModule } from "../../../../project.module";
describe('SummaryComponent', () => {
let component: SummaryComponent;
@ -165,7 +165,7 @@ describe('SummaryComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HarborLibraryModule
ProjectModule
],
providers: [
ErrorHandler,

View File

@ -3,9 +3,10 @@ import {
OnInit,
Input
} from "@angular/core";
import { ErrorHandler } from "../../../../utils/error-handler";
import { AdditionsService } from "../additions.service";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
@Component({
selector: "hbr-artifact-summary",

View File

@ -7,9 +7,10 @@ import { MarkdownModule, MarkdownService, MarkedOptions } from 'ngx-markdown';
import { BrowserModule } from '@angular/platform-browser';
import { ValuesComponent } from "./values.component";
import { AdditionsService } from "../additions.service";
import { ErrorHandler } from "../../../../utils/error-handler";
import { of } from "rxjs";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
describe('ValuesComponent', () => {
let component: ValuesComponent;

View File

@ -3,9 +3,10 @@ import {
Input,
OnInit,
} from "@angular/core";
import { ErrorHandler } from "../../../../utils/error-handler";
import { AdditionsService } from "../additions.service";
import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-link";
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
@Component({
selector: "hbr-artifact-values",

View File

@ -1,10 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtifactCommonPropertiesComponent } from './artifact-common-properties.component';
import { ExtraAttrs } from "../../../../../ng-swagger-gen/models/extra-attrs";
import { ClarityModule } from "@clr/angular";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { TranslateFakeLoader, TranslateLoader, TranslateModule, TranslateService } from "@ngx-translate/core";
import { ExtraAttrs } from "../../../../../../ng-swagger-gen/models/extra-attrs";
describe('ArtifactCommonPropertiesComponent', () => {
let component: ArtifactCommonPropertiesComponent;

View File

@ -1,8 +1,8 @@
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Artifact } from "../../../../../ng-swagger-gen/models/artifact";
import { DatePipe } from "@angular/common";
import { TranslateService } from "@ngx-translate/core";
import { formatSize } from "../../../utils/utils";
import { Artifact } from "../../../../../../ng-swagger-gen/models/artifact";
import { formatSize } from "../../../../../lib/utils/utils";
enum Types {
CREATED = 'created',

View File

@ -1,13 +1,23 @@
<div class="arrow-block" *ngIf="!withAdmiral">
<a (click)="goBackPro()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
<span class="back-icon"><</span>
<a (click)="goBackRep()">{{'REPOSITORY.REPOSITORIES'| translate}}</a>
<span class="back-icon"><</span>
<a (click)="goBack()">{{repositoryName}}</a>
<span *ngFor="let digest of referArtifactNameArray;let i = index">
&lt;<a (click)="jumpDigest(i)" >{{digest | slice:0:15}}</a></span>
</div>
<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">
<!-- <h2 class="custom-h2">{{repositoryName}}:{{artifactDetails?.digest | slice:0:15}}</h2> -->
<h2 class="custom-h2">{{artifact?.digest | slice:0:15}}</h2>
</div>
</div>
<ng-container *ngIf="artifact">
<ng-container *ngIf="!loading">
<!-- common properties -->
<artifact-common-properties [artifactDetails]="artifact"></artifact-common-properties>
@ -18,7 +28,7 @@
<!-- Additions -->
<artifact-additions [additionLinks]="artifact?.addition_links"></artifact-additions>
</ng-container>
<div *ngIf="!artifact" class="clr-row mt-3 center">
<div *ngIf="loading" class="clr-row mt-3 center">
<span class="spinner spinner-md"></span>
</div>

View File

@ -0,0 +1,20 @@
.arrow-block a{
cursor: pointer;
color: #007cbb;
font-size: 16px;
padding: 5px;
border-radius: 2px;
margin-right: 5px;
}
.back-icon {
color: gray;
}
.margin-top-5px {
margin-top: 5px;
}
.center {
justify-content: center;
align-items: center;
}

View File

@ -0,0 +1,110 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtifactSummaryComponent } from "./artifact-summary.component";
import { of } from "rxjs";
import { ClarityModule } from "@clr/angular";
import { NO_ERRORS_SCHEMA } from "@angular/core";
import { Artifact } from "../../../../../ng-swagger-gen/models/artifact";
import { ProjectService } from "../../../../lib/services";
import { ArtifactService } from "../../../../../ng-swagger-gen/services/artifact.service";
import { ErrorHandler } from "../../../../lib/utils/error-handler";
import { TranslateFakeLoader, TranslateLoader, TranslateModule } from "@ngx-translate/core";
import { ActivatedRoute, Router } from "@angular/router";
import { AppConfigService } from "../../../app-config.service";
describe('ArtifactSummaryComponent', () => {
const mockedArtifact: Artifact = {
id: 123,
type: 'IMAGE'
};
const fakedProjectService = {
getProject() {
return of({name: 'test'});
}
};
const fakedArtifactService = {
getArtifact() {
return of(mockedArtifact);
}
};
let component: ArtifactSummaryComponent;
let fixture: ComponentFixture<ArtifactSummaryComponent>;
const mockActivatedRoute = {
RouterparamMap: of({ get: (key) => 'value' }),
snapshot: {
params: {
id: 1,
repo: "test",
digest: "ABC",
subscribe: () => {
return of(null);
}
},
data: {
projectResolver: {
has_project_admin_role: true,
current_user_role_id: 3,
name: "demo"
}
}
},
data: of({
projectResolver: {
ismember: true,
role_name: 'master',
}
})
};
const fakedAppConfigService = {
getConfig: () => {
return {with_admiral: false};
}
};
const mockRouter = {
navigate: () => { }
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ClarityModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateFakeLoader,
}
})
],
declarations: [
ArtifactSummaryComponent
],
schemas: [
NO_ERRORS_SCHEMA
],
providers: [
{ provide: AppConfigService, useValue: fakedAppConfigService },
{ provide: Router, useValue: mockRouter },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: ProjectService, useValue: fakedProjectService },
{ provide: ArtifactService, useValue: fakedArtifactService },
ErrorHandler
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ArtifactSummaryComponent);
component = fixture.componentInstance;
component.repositoryName = 'demo';
component.artifactDigest = 'sha: acf4234f';
fixture.detectChanges();
});
it('should create and get artifactDetails', async () => {
expect(component).toBeTruthy();
await fixture.whenStable();
expect(component.artifact.type).toEqual('IMAGE');
});
});

View File

@ -0,0 +1,110 @@
import { Component, Output, EventEmitter, OnInit } from "@angular/core";
import { Artifact } from "../../../../../ng-swagger-gen/models/artifact";
import { ArtifactService } from "../../../../../ng-swagger-gen/services/artifact.service";
import { ErrorHandler } from "../../../../lib/utils/error-handler";
import { Label } from "../../../../../ng-swagger-gen/models/label";
import { ProjectService } from "../../../../lib/services";
import { ActivatedRoute, Router } from "@angular/router";
import { AppConfigService } from "../../../app-config.service";
import { Project } from "../../project";
import { finalize } from "rxjs/operators";
@Component({
selector: "artifact-summary",
templateUrl: "./artifact-summary.component.html",
styleUrls: ["./artifact-summary.component.scss"],
providers: []
})
export class ArtifactSummaryComponent implements OnInit {
tagId: string;
artifactDigest: string;
repositoryName: string;
projectId: string | number;
referArtifactNameArray: string[] = [];
labels: Label;
artifact: Artifact;
@Output()
backEvt: EventEmitter<any> = new EventEmitter<any>();
projectName: string;
loading: boolean = false;
constructor(
private projectService: ProjectService,
private artifactService: ArtifactService,
private errorHandler: ErrorHandler,
private route: ActivatedRoute,
private appConfigService: AppConfigService,
private router: Router
) {
}
get withAdmiral(): boolean {
return this.appConfigService.getConfig().with_admiral;
}
goBack(): void {
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName]);
}
goBackRep(): void {
this.router.navigate(["harbor", "projects", this.projectId, "repositories"]);
}
goBackPro(): void {
this.router.navigate(["harbor", "projects"]);
}
jumpDigest(index: number) {
const arr: string[] = this.referArtifactNameArray.slice(0, index + 1 );
if ( arr && arr.length) {
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName, "depth", arr.join('-')]);
} else {
this.router.navigate(["harbor", "projects", this.projectId, "repositories", this.repositoryName]);
}
}
ngOnInit(): void {
let depth = this.route.snapshot.params['depth'];
if (depth) {
this.referArtifactNameArray = depth.split('-');
}
this.repositoryName = this.route.snapshot.params["repo"];
this.artifactDigest = this.route.snapshot.params["digest"];
this.projectId = this.route.snapshot.params["id"];
if (this.repositoryName && this.artifactDigest) {
const resolverData = this.route.snapshot.data;
if (resolverData) {
const pro: Project = <Project>resolverData['projectResolver'];
this.projectName = pro.name;
if (this.projectName) {
this.getArtifactDetails();
}
}
}
}
getArtifactDetails(): void {
this.loading = true;
this.artifactService.getArtifact({
repositoryName: this.repositoryName,
reference: this.artifactDigest,
projectName: this.projectName,
}).pipe(finalize(() => this.loading = false))
.subscribe(response => {
this.artifact = response;
}, error => {
this.errorHandler.error(error);
});
}
onBack(): void {
this.backEvt.emit(this.repositoryName);
}
refreshArtifact() {
this.getArtifactDetails();
}
}

View File

@ -1,15 +1,15 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ErrorHandler } from "../../../utils/error-handler/error-handler";
import { ArtifactTagComponent } from './artifact-tag.component';
import { SharedModule } from '../../../utils/shared/shared.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { OperationService } from '../../operation/operation.service';
import { TagService } from '../../../services';
import { of } from 'rxjs';
import { SERVICE_CONFIG, IServiceConfig } from '../../../entities/service.config';
import { IServiceConfig, SERVICE_CONFIG } from "../../../../../lib/entities/service.config";
import { SharedModule } from "../../../../../lib/utils/shared/shared.module";
import { ErrorHandler } from "../../../../../lib/utils/error-handler";
import { TagService } from "../../../../../lib/services";
import { OperationService } from "../../../../../lib/components/operation/operation.service";
describe('ArtifactTagComponent', () => {
let component: ArtifactTagComponent;

View File

@ -1,19 +1,22 @@
import { Component, OnInit, Input, ViewChild, Output, EventEmitter } from '@angular/core';
import { Artifact } from '../artifact';
import { TagService } from '../../../services';
import { Tag } from '../../../../../ng-swagger-gen/models/tag';
import { ConfirmationButtons, ConfirmationTargets, ConfirmationState } from '../../../entities/shared.const';
import { ConfirmationMessage, ConfirmationDialogComponent, ConfirmationAcknowledgement } from '../../confirmation-dialog';
import { Observable, of, forkJoin } from 'rxjs';
import { OperateInfo, OperationState, operateChanges } from '../../operation/operate';
import { OperationService } from '../../operation/operation.service';
import { map, catchError } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { errorHandler as errorHandFn } from "../../../utils/shared/shared.utils";
import { NgForm } from '@angular/forms';
import { ErrorHandler } from '../../../utils/error-handler';
import { AVAILABLE_TIME } from '../artifact-list-tab.component';
import { AVAILABLE_TIME } from "../../artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component";
import {
ConfirmationAcknowledgement,
ConfirmationDialogComponent,
ConfirmationMessage
} from "../../../../../lib/components/confirmation-dialog";
import { OperationService } from "../../../../../lib/components/operation/operation.service";
import { Tag, TagService } from "../../../../../lib/services";
import { ErrorHandler } from "../../../../../lib/utils/error-handler";
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../../../lib/entities/shared.const";
import { operateChanges, OperateInfo, OperationState } from "../../../../../lib/components/operation/operate";
import { errorHandler } from "../../../../../lib/utils/shared/shared.utils";
import { Artifact } from "../artifact";
class InitTag {
name = "";
}
@ -43,7 +46,7 @@ export class ArtifactTagComponent implements OnInit {
private operationService: OperationService,
private tagService: TagService,
private translateService: TranslateService,
private errorHandler: ErrorHandler
private errorHandlerService: ErrorHandler
) { }
@ -64,7 +67,7 @@ export class ArtifactTagComponent implements OnInit {
this.newTagName = new InitTag();
this.refreshArtifact.emit();
}, error => {
this.errorHandler.error(error);
this.errorHandlerService.error(error);
});
}
removeTag() {
@ -127,7 +130,7 @@ export class ArtifactTagComponent implements OnInit {
operateChanges(operMessage, OperationState.success);
});
}), catchError(error => {
const message = errorHandFn(error);
const message = errorHandler(error);
this.translateService.get(message).subscribe(res =>
operateChanges(operMessage, OperationState.failure, res)
);

View File

@ -1,9 +1,7 @@
import { TestBed, inject } from '@angular/core/testing';
import { SharedModule } from '../utils/shared/shared.module';
import { SERVICE_CONFIG, IServiceConfig } from '../entities/service.config';
import { TagService, TagDefaultService } from './tag.service';
import { IServiceConfig, SERVICE_CONFIG } from "../../../../lib/entities/service.config";
import { SharedModule } from "../../../../lib/utils/shared/shared.module";
import { TagDefaultService, TagService } from "../../../../lib/services";
describe('TagService', () => {

View File

@ -1,18 +1,15 @@
import { Injectable, Inject } from "@angular/core";
import { HttpClient, HttpResponse } from "@angular/common/http";
import { SERVICE_CONFIG, IServiceConfig } from "../entities/service.config";
import {
buildHttpRequestOptions,
HTTP_JSON_OPTIONS,
HTTP_GET_OPTIONS,
buildHttpRequestOptionsWithObserveResponse
} from "../utils/utils";
import { RequestQueryParams } from "./RequestQueryParams";
import { Tag, Manifest } from "./interface";
import { Artifact } from "../components/artifact/artifact";
import { map, catchError } from "rxjs/operators";
import { Observable, throwError as observableThrowError, Subject } from "rxjs";
import { Manifest, RequestQueryParams } from "../../../../lib/services";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../lib/entities/service.config";
import {
buildHttpRequestOptionsWithObserveResponse,
HTTP_GET_OPTIONS,
HTTP_JSON_OPTIONS
} from "../../../../lib/utils/utils";
import { Artifact } from "./artifact";
/**

View File

@ -1,4 +1,5 @@
import { Label, Tag } from "../../services";
import { Label, Tag } from "../../../../lib/services";
export class Artifact {
id: number;

View File

@ -10,10 +10,10 @@
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { GridViewComponent } from './grid-view.component';
import { SharedModule } from '../../utils/shared/shared.module';
import { SERVICE_CONFIG, IServiceConfig } from '../../entities/service.config';
import { IServiceConfig, SERVICE_CONFIG } from "../../../../lib/entities/service.config";
import { SharedModule } from "../../../../lib/utils/shared/shared.module";
describe('GridViewComponent', () => {

View File

@ -24,8 +24,7 @@ import {
} from "@angular/core";
import { Subscription } from "rxjs";
import { TranslateService } from "@ngx-translate/core";
import { ScrollPosition } from "../../services/interface";
import { ScrollPosition } from "../../../../lib/services";
@Component({
selector: "hbr-gridview",

View File

@ -34,9 +34,9 @@
<clr-dg-column [clrDgSortBy]="'pull_count'">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPOSITORY.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]="r">
<clr-dg-cell><a href="javascript:void(0)" (click)="watchRepoClickEvt(r)"><span *ngIf="withAdmiral" class="list-img"><img [src]="getImgLink(r)"/></span>{{r.name}}</a></clr-dg-cell>
<clr-dg-cell><a href="javascript:void(0)" (click)="goIntoRepo(r)"><span *ngIf="withAdmiral" class="list-img"><img [src]="getImgLink(r)"/></span>{{r.name}}</a></clr-dg-cell>
<!-- to do -->
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
<clr-dg-cell>{{r.artifact_count}}</clr-dg-cell>
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
@ -58,7 +58,7 @@
[withAdmiral]="withAdmiral"
(loadNextPageEvent)="loadNextPage()">
<ng-template let-item="item">
<a class="card clickable" (click)="watchRepoClickEvt(item)">
<a class="card clickable" (click)="goIntoRepo(item)">
<div class="card-header">
<div [ngClass]="{'card-media-block': true, 'wrap': !withAdmiral }">
<img *ngIf="withAdmiral" [src]="getImgLink(item)"/>
@ -83,7 +83,7 @@
</div>
<div class="card-footer">
<clr-dropdown [clrCloseMenuOnItemClick]="false">
<button *ngIf="withAdmiral" type="button" class="btn btn-link" (click)="provisionItemEvent($event, item)" [disabled]="!hasProjectAdminRole">{{'REPOSITORY.DEPLOY' | translate}}</button>
<!--<button *ngIf="withAdmiral" type="button" class="btn btn-link" (click)="provisionItemEvent($event, item)" [disabled]="!hasProjectAdminRole">{{'REPOSITORY.DEPLOY' | translate}}</button>-->
<button type="button" class="btn btn-link" (click)="$event.stopPropagation()" [disabled]="!hasDeleteRepositoryPermission" clrDropdownTrigger>
{{'REPOSITORY.ACTION' | translate}}
<clr-icon shape="caret down"></clr-icon>

View File

@ -1,4 +1,44 @@
@import '../../mixin';
@mixin text-overflow
{
overflow: hidden;
text-overflow: ellipsis;
word-wrap:break-word;
white-space: nowrap
}
@mixin text-overflow-param($width) {
width: $width;
@include text-overflow;
}
@mixin grid-right-top-pos{
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
}
@mixin absolute-center($width:108px) {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: $width !important;
height: $width !important;
}
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin dropdown-as-action-button {
margin: .25rem .5rem .25rem 0;
}
.rightPos{
@include grid-right-top-pos;

View File

@ -2,27 +2,30 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from '../../utils/shared/shared.module';
import { RepositoryGridviewComponent } from './repository-gridview.component';
import { ErrorHandler } from '../../utils/error-handler/error-handler';
import { Repository, RepositoryItem, SystemInfo } from '../../services/interface';
import { SERVICE_CONFIG, IServiceConfig } from '../../entities/service.config';
import { RepositoryService } from '../../services/repository.service';
import { TagService, TagDefaultService } from '../../services/tag.service';
import { SystemInfoService } from '../../services/system-info.service';
import { OperationService } from "../operation/operation.service";
import { of } from "rxjs";
import { RepositoryService as NewRepositoryService } from "../../../../ng-swagger-gen/services/repository.service";
import { RepositoryGridviewComponent } from "./repository-gridview.component";
import {
ProjectDefaultService,
ProjectService,
RequestQueryParams,
RetagDefaultService,
RetagService
} from "../../services";
import { UserPermissionService } from "../../services/permission.service";
import { of } from "rxjs";
import { HarborLibraryModule } from "../../harbor-library.module";
Repository,
RepositoryItem,
RequestQueryParams, RetagDefaultService, RetagService,
SystemInfo, SystemInfoService,
TagDefaultService,
TagService, UserPermissionService
} from "../../../lib/services";
import { IServiceConfig, SERVICE_CONFIG } from "../../../lib/entities/service.config";
import { delay } from 'rxjs/operators';
import { RepositoryService as NewRepositoryService } from "../../../../ng-swagger-gen/services/repository.service";
import { SharedModule } from "../../../lib/utils/shared/shared.module";
import { ErrorHandler } from "../../../lib/utils/error-handler";
import { RepositoryService } from "./repository.service";
import { OperationService } from "../../../lib/components/operation/operation.service";
import { ProjectModule } from "../project.module";
import { ActivatedRoute } from "@angular/router";
import { Repository as NewRepository } from "../../../../ng-swagger-gen/models/repository";
import { StrictHttpResponse as __StrictHttpResponse } from '../../../../ng-swagger-gen/strict-http-response';
describe('RepositoryComponentGridview (inline template)', () => {
let compRepo: RepositoryGridviewComponent;
@ -38,15 +41,14 @@ describe('RepositoryComponentGridview (inline template)', () => {
"has_ca_root": false,
"harbor_version": "v1.1.1-rc1-160-g565110d"
};
let mockRepoData: RepositoryItem[] = [
let mockRepoData: NewRepository[] = [
{
"id": 1,
"name": "library/busybox",
"project_id": 1,
"description": "asdfsadf",
"pull_count": 0,
"star_count": 0,
"tags_count": 1
"artifact_count": 1
},
{
"id": 2,
@ -54,30 +56,22 @@ describe('RepositoryComponentGridview (inline template)', () => {
"project_id": 1,
"description": "asdf",
"pull_count": 0,
"star_count": 0,
"tags_count": 1
"artifact_count": 1
}
];
let mockRepoNginxData: RepositoryItem[] = [
let mockRepoNginxData: NewRepository[] = [
{
"id": 2,
"name": "library/nginx",
"project_id": 1,
"description": "asdf",
"pull_count": 0,
"star_count": 0,
"tags_count": 1
"artifact_count": 1
}
];
let mockRepo: Repository = {
metadata: { xTotalCount: 2 },
data: mockRepoData
};
let mockNginxRepo: Repository = {
metadata: { xTotalCount: 2 },
data: mockRepoNginxData
};
let mockRepo: NewRepository[] = mockRepoData;
let mockNginxRepo: NewRepository[] = mockRepoNginxData;
let config: IServiceConfig = {
repositoryBaseEndpoint: '/api/repository/testing',
systemInfoEndpoint: '/api/systeminfo/testing',
@ -94,11 +88,11 @@ describe('RepositoryComponentGridview (inline template)', () => {
}
};
const fakedRepositoryService = {
getRepositories(projectId: number, name: string, param?: RequestQueryParams) {
if (name === 'nginx') {
return of(mockNginxRepo);
listRepositoriesResponse(params: NewRepositoryService.ListRepositoriesParams) {
if (params.name === 'nginx') {
return of({headers: new Map(), body: mockNginxRepo});
}
return of(mockRepo).pipe(delay(0));
return of({headers: new Map(), body: mockRepo}).pipe(delay(0));
}
};
const fakedUserPermissionService = {
@ -106,15 +100,24 @@ describe('RepositoryComponentGridview (inline template)', () => {
return of(true);
}
};
const fakedActivatedRoute = {
snapshot: {
parent: {
params: {
id: "1"
}
}
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
RouterTestingModule,
HarborLibraryModule
ProjectModule
],
providers: [
{ provide: ActivatedRoute, useValue: fakedActivatedRoute },
{ provide: ErrorHandler, useValue: fakedErrorHandler },
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: RepositoryService, useValue: fakedRepositoryService },
@ -150,30 +153,4 @@ describe('RepositoryComponentGridview (inline template)', () => {
it('should create', async(() => {
expect(compRepo).toBeTruthy();
}));
it('should load and render data', async(() => {
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges();
let deRepo: DebugElement = fixtureRepo.debugElement.query(del => del.classes['datagrid-cell']);
expect(deRepo).toBeTruthy();
let elRepo: HTMLElement = deRepo.nativeElement;
expect(elRepo).toBeTruthy();
expect(elRepo.textContent).toEqual('library/busybox');
});
}));
it('should filter data by keyword', async () => {
fixtureRepo.detectChanges();
await fixtureRepo.whenStable();
compRepo.doSearchRepoNames('nginx');
fixtureRepo.detectChanges();
await fixtureRepo.whenStable();
let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('.datagrid-cell'));
expect(de).toBeTruthy();
expect(compRepo.repositories.length).toEqual(1);
expect(de.length).toEqual(4);
let el: HTMLElement = de[1].nativeElement;
expect(el).toBeTruthy();
expect(el.textContent).toEqual('library/nginx');
});
});

View File

@ -0,0 +1,455 @@
import {
Component,
Input,
Output,
OnInit,
ViewChild,
EventEmitter,
OnChanges,
SimpleChanges,
Inject, OnDestroy
} from "@angular/core";
import { forkJoin, Subscription } from "rxjs";
import { debounceTime, distinctUntilChanged, switchMap } from "rxjs/operators";
import { TranslateService } from "@ngx-translate/core";
import { map, catchError } from "rxjs/operators";
import { Observable, throwError as observableThrowError } from "rxjs";
import { ClrDatagridStateInterface } from "@clr/angular";
import {
RepositoryService as NewRepositoryService
} from "../../../../ng-swagger-gen/services/repository.service";
import {
Repository,
RepositoryItem, RequestQueryParams,
SystemInfo,
SystemInfoService,
TagService, UserPermissionService, USERSTATICPERMISSION
} from "../../../lib/services";
import { FilterComponent } from "../../../lib/components/filter/filter.component";
import { calculatePage, clone, DEFAULT_PAGE_SIZE } from "../../../lib/utils/utils";
import { IServiceConfig, SERVICE_CONFIG } from "../../../lib/entities/service.config";
import { ErrorHandler } from "../../../lib/utils/error-handler";
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../lib/entities/shared.const";
import { operateChanges, OperateInfo, OperationState } from "../../../lib/components/operation/operate";
import {
ConfirmationAcknowledgement,
ConfirmationDialogComponent,
ConfirmationMessage
} from "../../../lib/components/confirmation-dialog";
import { OperationService } from "../../../lib/components/operation/operation.service";
import { errorHandler } from "../../../lib/utils/shared/shared.utils";
import { Project } from "../project";
import { ActivatedRoute, Router } from "@angular/router";
import { SessionService } from "../../shared/session.service";
import { RepositoryDefaultService } from "./repository.service";
import { GridViewComponent } from "./gridview/grid-view.component";
import { Repository as NewRepository } from "../../../../ng-swagger-gen/models/repository";
import { StrictHttpResponse as __StrictHttpResponse } from '../../../../ng-swagger-gen/strict-http-response';
@Component({
selector: "hbr-repository-gridview",
templateUrl: "./repository-gridview.component.html",
styleUrls: ["./repository-gridview.component.scss"],
})
export class RepositoryGridviewComponent implements OnChanges, OnInit, OnDestroy {
signedCon: { [key: string]: any | string[] } = {};
downloadLink: string;
@Input() urlPrefix: string;
projectId: number;
hasProjectAdminRole: boolean;
hasSignedIn: boolean;
projectName: string;
mode = 'standalone';
@Output() repoProvisionEvent = new EventEmitter<NewRepository>();
@Output() addInfoEvent = new EventEmitter<NewRepository>();
lastFilteredRepoName: string;
repositories: NewRepository[] = [];
repositoriesCopy: NewRepository[] = [];
systemInfo: SystemInfo;
selectedRow: NewRepository[] = [];
loading = true;
isCardView: boolean;
cardHover = false;
listHover = false;
pageSize: number = DEFAULT_PAGE_SIZE;
currentPage = 1;
totalCount = 0;
currentState: ClrDatagridStateInterface;
@ViewChild("confirmationDialog", {static: false})
confirmationDialog: ConfirmationDialogComponent;
@ViewChild("gridView", {static: false}) gridView: GridViewComponent;
hasCreateRepositoryPermission: boolean;
hasDeleteRepositoryPermission: boolean;
@ViewChild(FilterComponent, {static: true})
filterComponent: FilterComponent;
searchSub: Subscription;
constructor(@Inject(SERVICE_CONFIG) private configInfo: IServiceConfig,
private errorHandlerService: ErrorHandler,
private translateService: TranslateService,
private repositoryService: RepositoryDefaultService,
private newRepoService: NewRepositoryService,
private systemInfoService: SystemInfoService,
private tagService: TagService,
private operationService: OperationService,
private userPermissionService: UserPermissionService,
private route: ActivatedRoute,
private session: SessionService,
private router: Router,
) {
if (this.configInfo && this.configInfo.systemInfoEndpoint) {
this.downloadLink = this.configInfo.systemInfoEndpoint + "/getcert";
}
}
public get registryUrl(): string {
return this.systemInfo ? this.systemInfo.registry_url : "";
}
public get withAdmiral(): boolean {
return this.mode === "admiral";
}
get canDownloadCert(): boolean {
return this.systemInfo && this.systemInfo.has_ca_root;
}
goIntoRepo(repoEvt: RepositoryItem): void {
let linkUrl = ['harbor', 'projects', repoEvt.project_id, 'repositories', repoEvt.name.split('/')[1]];
this.router.navigate(linkUrl);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes["projectId"] && changes["projectId"].currentValue) {
this.refresh();
}
}
ngOnInit(): void {
this.projectId = this.route.snapshot.parent.params['id'];
let resolverData = this.route.snapshot.parent.data;
if (resolverData) {
let pro: Project = <Project>resolverData['projectResolver'];
this.hasProjectAdminRole = pro.has_project_admin_role;
this.projectName = pro.name;
}
this.hasSignedIn = this.session.getCurrentUser() !== null;
// Get system info for tag views
this.getSystemInfo();
this.isCardView = this.mode === "admiral";
this.lastFilteredRepoName = "";
this.getHelmChartVersionPermission(this.projectId);
if (!this.searchSub) {
this.searchSub = this.filterComponent.filterTerms.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(repoName => {
this.lastFilteredRepoName = repoName;
this.currentPage = 1;
// Pagination
let params: NewRepositoryService.ListRepositoriesParams = {
projectName: this.projectName,
page: this.currentPage,
pageSize: this.pageSize,
name: this.lastFilteredRepoName
};
this.loading = true;
return this.newRepoService.listRepositoriesResponse(params);
})
).subscribe((repo: __StrictHttpResponse<Array<NewRepository>>) => {
this.totalCount = +repo.headers.get('x-total-count');
this.repositories = repo.body;
this.loading = false;
}, error => {
this.loading = false;
this.errorHandlerService.error(error);
});
}
}
getSystemInfo() {
this.systemInfoService.getSystemInfo()
.subscribe(systemInfo => (this.systemInfo = systemInfo)
, error => this.errorHandlerService.error(error));
}
ngOnDestroy() {
if (this.searchSub) {
this.searchSub.unsubscribe();
this.searchSub = null;
}
}
confirmDeletion(message: ConfirmationAcknowledgement) {
this.loading = true;
// forkJoin(...repArr).subscribe(() => {
if (message &&
message.source === ConfirmationTargets.REPOSITORY &&
message.state === ConfirmationState.CONFIRMED) {
let repoLists = message.data;
if (repoLists && repoLists.length) {
let observableLists: any[] = [];
repoLists.forEach(repo => {
observableLists.push(this.delOperate(repo));
});
forkJoin(observableLists).subscribe((item) => {
this.selectedRow = [];
this.refresh();
let st: ClrDatagridStateInterface = this.getStateAfterDeletion();
if (!st) {
this.refresh();
} else {
this.clrLoad(st);
}
}, error => {
this.errorHandlerService.error(error);
this.loading = false;
this.refresh();
});
}
}
}
delOperate(repo: NewRepository): Observable<any> {
// init operation info
let operMessage = new OperateInfo();
operMessage.name = 'OPERATION.DELETE_REPO';
operMessage.data.id = repo.id;
operMessage.state = OperationState.progressing;
operMessage.data.name = repo.name;
this.operationService.publishInfo(operMessage);
return this.newRepoService
.deleteRepository({
repositoryName: repo.name,
projectName: this.projectName
})
.pipe(map(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS').subscribe(res => {
operateChanges(operMessage, OperationState.success);
});
}), catchError(error => {
const message = errorHandler(error);
this.translateService.get(message).subscribe(res =>
operateChanges(operMessage, OperationState.failure, res)
);
return observableThrowError(message);
}));
}
doSearchRepoNames(repoName: string) {
this.lastFilteredRepoName = repoName;
this.currentPage = 1;
let st: ClrDatagridStateInterface = this.currentState;
if (!st || !st.page) {
st = {page: {}};
}
st.page.size = this.pageSize;
st.page.from = 0;
st.page.to = this.pageSize - 1;
this.clrLoad(st);
}
deleteRepos(repoLists: NewRepository[]) {
if (repoLists && repoLists.length) {
let repoNames: string[] = [];
repoLists.forEach(repo => {
repoNames.push(repo.name);
});
this.confirmationDialogSet(
'REPOSITORY.DELETION_TITLE_REPO',
'',
repoNames.join(','),
repoLists,
'REPOSITORY.DELETION_SUMMARY_REPO',
ConfirmationButtons.DELETE_CANCEL);
}
}
confirmationDialogSet(summaryTitle: string, signature: string,
repoName: string, repoLists: NewRepository[],
summaryKey: string, button: ConfirmationButtons): void {
this.translateService.get(summaryKey,
{
repoName: repoName,
signedImages: signature,
}).subscribe((res: string) => {
summaryKey = res;
let message = new ConfirmationMessage(
summaryTitle,
summaryKey,
repoName,
repoLists,
ConfirmationTargets.REPOSITORY,
button);
this.confirmationDialog.open(message);
});
}
itemAddInfoEvent(evt: any, repo: NewRepository): void {
evt.stopPropagation();
let repoCopy = clone(repo);
repoCopy.name = this.registryUrl + ":443/" + repoCopy.name;
this.addInfoEvent.emit(repoCopy);
}
deleteItemEvent(evt: any, item: NewRepository): void {
evt.stopPropagation();
this.deleteRepos([item]);
}
refresh() {
this.doSearchRepoNames("");
}
loadNextPage() {
this.currentPage = this.currentPage + 1;
// Pagination
let params: NewRepositoryService.ListRepositoriesParams = {
projectName: this.projectName,
page: this.currentPage,
pageSize: this.pageSize,
name: this.lastFilteredRepoName
};
this.loading = true;
this.newRepoService.listRepositoriesResponse(
params
)
.subscribe((repo: __StrictHttpResponse<Array<NewRepository>>) => {
this.totalCount = +repo.headers.get('x-total-count');
this.repositoriesCopy = repo.body;
this.repositories = this.repositories.concat(this.repositoriesCopy);
this.loading = false;
}, error => {
this.loading = false;
this.errorHandlerService.error(error);
});
}
clrLoad(state: ClrDatagridStateInterface): void {
if (!state || !state.page) {
return;
}
this.selectedRow = [];
// Keep it for future filtering and sorting
this.currentState = state;
let pageNumber: number = calculatePage(state);
if (pageNumber <= 0) {
pageNumber = 1;
}
// Pagination
let params: NewRepositoryService.ListRepositoriesParams = {
projectName: this.projectName,
page: pageNumber,
pageSize: this.pageSize,
name: this.lastFilteredRepoName
};
if (state.filters && state.filters.length) {
state.filters.forEach(item => {
params[item.property] = item.value;
});
}
if (state.sort && state.sort.by) {
// params = params.set(`sort`, `${(state.sort.reverse ? `-` : ``)}${state.sort.by as string}`);
}
this.loading = true;
this.newRepoService.listRepositoriesResponse(
params
)
.subscribe((repo: __StrictHttpResponse<Array<NewRepository>>) => {
this.totalCount = +repo.headers.get('x-total-count');
this.repositories = repo.body;
this.signedCon = {};
this.loading = false;
}, error => {
this.loading = false;
this.errorHandlerService.error(error);
});
}
getStateAfterDeletion(): ClrDatagridStateInterface {
let total: number = this.totalCount - 1;
if (total <= 0) {
return null;
}
let totalPages: number = Math.ceil(total / this.pageSize);
let targetPageNumber: number = this.currentPage;
if (this.currentPage > totalPages) {
targetPageNumber = totalPages; // Should == currentPage -1
}
let st: ClrDatagridStateInterface = this.currentState;
if (!st) {
st = {page: {}};
}
st.page.size = this.pageSize;
st.page.from = (targetPageNumber - 1) * this.pageSize;
st.page.to = targetPageNumber * this.pageSize - 1;
return st;
}
getImgLink(repo: NewRepository): string {
return "/container-image-icons?container-image=" + repo.name;
}
showCard(cardView: boolean) {
if (this.isCardView === cardView) {
return;
}
this.isCardView = cardView;
this.refresh();
}
mouseEnter(itemName: string) {
if (itemName === "card") {
this.cardHover = true;
} else {
this.listHover = true;
}
}
mouseLeave(itemName: string) {
if (itemName === "card") {
this.cardHover = false;
} else {
this.listHover = false;
}
}
isHovering(itemName: string) {
if (itemName === "card") {
return this.cardHover;
} else {
return this.listHover;
}
}
getHelmChartVersionPermission(projectId: number): void {
let hasCreateRepositoryPermission = this.userPermissionService.getPermission(this.projectId,
USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.CREATE);
let hasDeleteRepositoryPermission = this.userPermissionService.getPermission(this.projectId,
USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.DELETE);
forkJoin(hasCreateRepositoryPermission, hasDeleteRepositoryPermission).subscribe(permissions => {
this.hasCreateRepositoryPermission = permissions[0] as boolean;
this.hasDeleteRepositoryPermission = permissions[1] as boolean;
}, error => this.errorHandlerService.error(error));
}
}

View File

@ -1,8 +1,9 @@
import { TestBed, inject } from '@angular/core/testing';
import { RepositoryService, RepositoryDefaultService } from './repository.service';
import { SharedModule } from '../utils/shared/shared.module';
import { SERVICE_CONFIG, IServiceConfig } from '../entities/service.config';
import { IServiceConfig, SERVICE_CONFIG } from "../../../lib/entities/service.config";
import { SharedModule } from "../../../lib/utils/shared/shared.module";
describe('RepositoryService', () => {
const mockConfig: IServiceConfig = {

View File

@ -1,12 +1,10 @@
import { RequestQueryParams } from './RequestQueryParams';
import { Repository, RepositoryItem } from './interface';
import { Injectable, Inject } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { SERVICE_CONFIG, IServiceConfig } from '../entities/service.config';
import { buildHttpRequestOptions, buildHttpRequestOptionsWithObserveResponse, HTTP_JSON_OPTIONS } from '../utils/utils';
import { map, catchError } from "rxjs/operators";
import { Observable, throwError as observableThrowError } from "rxjs";
import { Repository, RepositoryItem, RequestQueryParams } from "../../../lib/services";
import { IServiceConfig, SERVICE_CONFIG } from "../../../lib/entities/service.config";
import { buildHttpRequestOptionsWithObserveResponse, HTTP_JSON_OPTIONS } from "../../../lib/utils/utils";
/**
* Define service methods for handling the repository related things.

View File

@ -1,23 +1,21 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { VulnerabilitySummary } from '../../services';
import { ResultBarChartComponent } from './result-bar-chart.component';
import { ResultTipComponent } from './result-tip.component';
import {
ScanningResultService,
ScanningResultDefaultService,
ArtifactService,
ArtifactDefaultService,
JobLogService,
JobLogDefaultService
} from '../../services';
import { SERVICE_CONFIG, IServiceConfig } from '../../entities/service.config';
import { ErrorHandler } from '../../utils/error-handler';
import { SharedModule } from '../../utils/shared/shared.module';
import { VULNERABILITY_SCAN_STATUS } from '../../utils/utils';
import { ResultTipHistogramComponent } from "./result-tip-histogram/result-tip-histogram.component";
import { HistogramChartComponent } from "./histogram-chart/histogram-chart.component";
import { ChannelService } from "../../services/channel.service";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../lib/entities/service.config";
import {
JobLogDefaultService,
JobLogService,
ScanningResultDefaultService,
ScanningResultService,
VulnerabilitySummary
} from "../../../../lib/services";
import { VULNERABILITY_SCAN_STATUS } from "../../../../lib/utils/utils";
import { SharedModule } from "../../../../lib/utils/shared/shared.module";
import { ErrorHandler } from "../../../../lib/utils/error-handler";
import { ChannelService } from "../../../../lib/services/channel.service";
import { ArtifactDefaultService, ArtifactService } from "../artifact/artifact.service";
describe('ResultBarChartComponent (inline template)', () => {
let component: ResultBarChartComponent;
@ -53,6 +51,7 @@ describe('ResultBarChartComponent (inline template)', () => {
providers: [
ErrorHandler,
ChannelService,
ArtifactDefaultService,
{ provide: SERVICE_CONFIG, useValue: testConfig },
{ provide: ArtifactService, useValue: ArtifactDefaultService },
{ provide: ScanningResultService, useValue: ScanningResultDefaultService },

View File

@ -6,19 +6,14 @@ import {
ChangeDetectorRef, Output, EventEmitter,
} from '@angular/core';
import { Subscription , timer} from "rxjs";
import { clone, DEFAULT_SUPPORTED_MIME_TYPE, VULNERABILITY_SCAN_STATUS } from '../../utils/utils';
import {
VulnerabilitySummary,
TagService,
ScanningResultService,
ScannerVo, ArtifactService
} from '../../services';
import { ErrorHandler } from '../../utils/error-handler';
import { JobLogService } from "../../services";
import { finalize } from "rxjs/operators";
import { ChannelService } from "../../services/channel.service";
import { Artifact } from '../artifact/artifact';
import { ScannerVo, ScanningResultService, VulnerabilitySummary } from "../../../../lib/services";
import { ArtifactDefaultService } from "../artifact/artifact.service";
import { ErrorHandler } from "../../../../lib/utils/error-handler";
import { ChannelService } from "../../../../lib/services/channel.service";
import { clone, DEFAULT_SUPPORTED_MIME_TYPE, VULNERABILITY_SCAN_STATUS } from "../../../../lib/utils/utils";
import { Artifact } from "../artifact/artifact";
const STATE_CHECK_INTERVAL: number = 3000; // 3s
const RETRY_TIMES: number = 3;
@ -39,18 +34,15 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
stateCheckTimer: Subscription;
scanSubscription: Subscription;
timerHandler: any;
@Output()
submitFinish: EventEmitter<boolean> = new EventEmitter<boolean>();
constructor(
// private tagService: TagService,
private artifactService: ArtifactService,
private artifactService: ArtifactDefaultService,
private scanningService: ScanningResultService,
private errorHandler: ErrorHandler,
private channel: ChannelService,
private ref: ChangeDetectorRef,
// private jobLogService: JobLogService,
) { }
ngOnInit(): void {

View File

@ -1,17 +1,19 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { VulnerabilityItem } from '../../services';
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { ResultGridComponent } from './result-grid.component';
import { ScanningResultService, ScanningResultDefaultService } from '../../services/scanning.service';
import { SERVICE_CONFIG, IServiceConfig } from '../../entities/service.config';
import { ErrorHandler } from '../../utils/error-handler';
import { SharedModule } from '../../utils/shared/shared.module';
import {ChannelService} from "../../services/channel.service";
import { UserPermissionService, UserPermissionDefaultService } from "../../services/permission.service";
import { USERSTATICPERMISSION } from "../../services/permission-static";
import { of } from "rxjs";
import { DEFAULT_SUPPORTED_MIME_TYPE, VULNERABILITY_SEVERITY } from "../../utils/utils";
import { FilterComponent } from "../filter/filter.component";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../lib/entities/service.config";
import {
ScanningResultDefaultService,
ScanningResultService,
UserPermissionDefaultService,
UserPermissionService, USERSTATICPERMISSION, VulnerabilityItem
} from "../../../../lib/services";
import { SharedModule } from "../../../../lib/utils/shared/shared.module";
import { FilterComponent } from "../../../../lib/components/filter/filter.component";
import { ChannelService } from "../../../../lib/services/channel.service";
import { ErrorHandler } from "../../../../lib/utils/error-handler";
import { DEFAULT_SUPPORTED_MIME_TYPE, VULNERABILITY_SEVERITY } from "../../../../lib/utils/utils";
describe('ResultGridComponent (inline template)', () => {
let component: ResultGridComponent;
let fixture: ComponentFixture<ResultGridComponent>;

View File

@ -1,17 +1,16 @@
import { Component, OnInit, Input } from '@angular/core';
import {
ScanningResultService,
VulnerabilityItem
} from '../../services';
import { ErrorHandler } from '../../utils/error-handler';
import { forkJoin } from "rxjs";
import { ChannelService } from "../../services/channel.service";
import { UserPermissionService } from "../../services";
import { USERSTATICPERMISSION } from "../../services";
import { DEFAULT_SUPPORTED_MIME_TYPE, SEVERITY_LEVEL_MAP, VULNERABILITY_SEVERITY } from '../../utils/utils';
import { finalize } from "rxjs/operators";
import { ClrDatagridComparatorInterface, ClrLoadingState } from "@clr/angular";
import {
ScanningResultService,
UserPermissionService,
USERSTATICPERMISSION,
VulnerabilityItem
} from "../../../../lib/services";
import { ChannelService } from "../../../../lib/services/channel.service";
import { ErrorHandler } from "../../../../lib/utils/error-handler";
import { DEFAULT_SUPPORTED_MIME_TYPE, SEVERITY_LEVEL_MAP, VULNERABILITY_SEVERITY } from "../../../../lib/utils/utils";
@Component({
selector: 'hbr-vulnerabilities-grid',

View File

@ -1,7 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { ScannerVo, VulnerabilitySummary } from "../../../services";
import { VULNERABILITY_SCAN_STATUS, VULNERABILITY_SEVERITY } from "../../../utils/utils";
import { TranslateService } from "@ngx-translate/core";
import { ScannerVo, VulnerabilitySummary } from "../../../../../lib/services";
import { VULNERABILITY_SCAN_STATUS, VULNERABILITY_SEVERITY } from "../../../../../lib/utils/utils";
const MIN = 60;
const MIN_STR = "min ";

View File

@ -1,12 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { VulnerabilitySummary } from '../../services';
import { ResultTipComponent } from './result-tip.component';
import { SharedModule } from '../../utils/shared/shared.module';
import { IServiceConfig, SERVICE_CONFIG } from "../../../../lib/entities/service.config";
import { UserPermissionDefaultService, UserPermissionService, VulnerabilitySummary } from "../../../../lib/services";
import { VULNERABILITY_SCAN_STATUS } from "../../../../lib/utils/utils";
import { SharedModule } from "../../../../lib/utils/shared/shared.module";
import { SERVICE_CONFIG, IServiceConfig } from '../../entities/service.config';
import { VULNERABILITY_SCAN_STATUS } from '../../utils/utils';
import { UserPermissionService, UserPermissionDefaultService } from "../../services/permission.service";
describe('ResultTipComponent (inline template)', () => {
let component: ResultTipComponent;

View File

@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { VulnerabilitySummary, VulnerabilitySeverity } from '../../services';
import { VULNERABILITY_SCAN_STATUS } from '../../utils/utils';
import { VulnerabilitySeverity, VulnerabilitySummary } from "../../../../lib/services";
import { VULNERABILITY_SCAN_STATUS } from "../../../../lib/utils/utils";
export const MIN_TIP_WIDTH = 5;
export const MAX_TIP_WIDTH = 100;

View File

@ -1,15 +0,0 @@
<div>
<div class="breadcrumb" *ngIf="!withAdmiral">
<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>
<artifact-list [repoName]="repoName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole"
[projectId]="projectId" [memberRoleID]="projectMemberRoleId" [isGuest]="isGuest"
(tagClickEvent)="watchTagClickEvt($event)" (backEvt)="watchGoBackEvt($event)" (putArtifactReferenceArr)="putArtifactReferenceArr($event)"></artifact-list>
</div>

View File

@ -1,17 +0,0 @@
<div>
<div class="arrow-block" *ngIf="!withAdmiral">
<a (click)="goBackPro()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
<span class="back-icon"><</span>
<a (click)="goBackRep()">{{'REPOSITORY.REPOSITORIES'| translate}}</a>
<span class="back-icon"><</span>
<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>
<artifact-summary (backEvt)="goBack($event)"
[artifactDigest]="artifactDigest"
[withAdmiral]="withAdmiral"
[projectId]="projectId"
[repositoryName]="repositoryName"></artifact-summary>
</div>

View File

@ -1,11 +0,0 @@
.arrow-block a{
cursor: pointer;
color: #007cbb;
font-size: 16px;
padding: 5px;
border-radius: 2px;
margin-right: 5px;
}
.back-icon {
color: gray;
}

View File

@ -1,92 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
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';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
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('ArtifactSummaryPageComponent', () => {
let component: ArtifactSummaryPageComponent;
let fixture: ComponentFixture<ArtifactSummaryPageComponent>;
const mockSessionService = {
getCurrentUser: () => { }
};
const mockAppConfigService = {
getConfig: () => {
return {
registry_storage_provider_name : ""
};
}
};
const mockRouter = {
navigate: () => { }
};
const mockActivatedRoute = {
RouterparamMap: of({ get: (key) => 'value' }),
snapshot: {
params: {
id: 1,
repo: "ere",
tag: "33"
},
parent: {
params: { id: 1 },
},
data: {
projectResolver: {
has_project_admin_role: true,
current_user_role_id: 3,
}
}
},
data: of({
projectResolver: {
ismember: true,
role_name: 'master',
}
})
};
beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
imports: [
BrowserAnimationsModule,
ClarityModule,
TranslateModule.forRoot(),
FormsModule,
RouterTestingModule,
NoopAnimationsModule,
HttpClientTestingModule
],
declarations: [ArtifactSummaryPageComponent],
providers: [
TranslateService,
{ provide: SessionService, useValue: mockSessionService },
{ provide: AppConfigService, useValue: mockAppConfigService },
{ provide: Router, useValue: mockRouter },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ArtifactSummaryPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,72 +0,0 @@
// 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 { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {AppConfigService} from "../../app-config.service";
@Component({
selector: 'artifact-summary-page',
templateUrl: 'artifact-summary-page.component.html',
styleUrls: ["artifact-summary-page.component.scss"]
})
export class ArtifactSummaryPageComponent implements OnInit, OnDestroy {
tagId: string;
artifactDigest: string;
repositoryName: string;
projectId: string | number;
referArtifactNameArray: string[] = [];
constructor(
private route: ActivatedRoute,
private appConfigService: AppConfigService,
private router: Router
) {
}
ngOnInit(): void {
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(repositoryName: string): void {
this.router.navigate(["harbor", "projects", this.projectId, "repositories", repositoryName]);
}
goBackRep(): void {
this.router.navigate(["harbor", "projects", this.projectId, "repositories"]);
}
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]);
}
}

View File

@ -1,5 +0,0 @@
<div>
<hbr-repository-gridview [projectId]="projectId" [projectName]="projectName" [hasSignedIn]="hasSignedIn"
[hasProjectAdminRole]="hasProjectAdminRole" [mode]="mode"
(repoClickEvent)="watchRepoClickEvent($event)"></hbr-repository-gridview>
</div>

View File

@ -1,67 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { RepositoryPageComponent } from './repository-page.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { SessionService } from '../shared/session.service';
describe('RepositoryPageComponent', () => {
let component: RepositoryPageComponent;
let fixture: ComponentFixture<RepositoryPageComponent>;
const mockActivatedRoute = {
RouterparamMap: of({ get: (key) => 'value' }),
snapshot: {
parent: {
params: { id: 1 },
data: {
projectResolver: {
ismember: true,
name: 'library',
}
}
}
}
};
const mockSessionService = {
getCurrentUser: () => { }
};
beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
imports: [
BrowserAnimationsModule,
ClarityModule,
TranslateModule.forRoot(),
FormsModule,
RouterTestingModule,
NoopAnimationsModule,
HttpClientTestingModule
],
declarations: [RepositoryPageComponent],
providers: [
TranslateService,
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: SessionService, useValue: mockSessionService },
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RepositoryPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,52 +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, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Project } from '../project/project';
import { SessionService } from '../shared/session.service';
import { RepositoryItem } from "../../lib/services";
@Component({
selector: 'repository',
templateUrl: 'repository-page.component.html'
})
export class RepositoryPageComponent implements OnInit {
projectId: number;
hasProjectAdminRole: boolean;
hasSignedIn: boolean;
projectName: string;
mode = 'standalone';
constructor(
private route: ActivatedRoute,
private session: SessionService,
private router: Router,
) {
}
ngOnInit(): void {
this.projectId = this.route.snapshot.parent.params['id'];
let resolverData = this.route.snapshot.parent.data;
if (resolverData) {
let pro: Project = <Project>resolverData['projectResolver'];
this.hasProjectAdminRole = pro.has_project_admin_role;
this.projectName = pro.name;
}
this.hasSignedIn = this.session.getCurrentUser() !== null;
}
watchRepoClickEvent(repoEvt: RepositoryItem): void {
let linkUrl = ['harbor', 'projects', repoEvt.project_id, 'repositories', repoEvt.name.split('/')[1]];
this.router.navigate(linkUrl);
}
}

View File

@ -1,42 +0,0 @@
// 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 { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared/shared.module';
import { RepositoryPageComponent } from './repository-page.component';
import { ArtifactListPageComponent } from './artifact-list-page/artifact-list-page.component';
import { TopRepoComponent } from './top-repo/top-repo.component';
import { ArtifactSummaryPageComponent } from './artifact-summary-page/artifact-summary-page.component';
@NgModule({
imports: [
SharedModule,
RouterModule
],
declarations: [
RepositoryPageComponent,
ArtifactListPageComponent,
TopRepoComponent,
ArtifactSummaryPageComponent
],
exports: [
RepositoryPageComponent,
TopRepoComponent,
ArtifactSummaryPageComponent
],
providers: []
})
export class RepositoryModule { }

View File

@ -1,12 +0,0 @@
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();
});
});

View File

@ -1,74 +0,0 @@
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);
})
);
}
}

View File

@ -1,29 +0,0 @@
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 }
]
});
});
it('should be created', inject([LeavingArtifactSummaryRouteDeactivate], (service: LeavingArtifactSummaryRouteDeactivate) => {
expect(service).toBeTruthy();
}));
});

View File

@ -1,45 +0,0 @@
// 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 { Injectable } from '@angular/core';
import {
CanDeactivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '@angular/router';
import { ConfirmationDialogService } from '../confirmation-dialog/confirmation-dialog.service';
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
import { ConfirmationState, ConfirmationTargets } from '../shared.const';
import { ArtifactListPageComponent } from '../../repository/artifact-list-page/artifact-list-page.component';
import { Observable } from 'rxjs';
@Injectable()
export class LeavingArtifactSummaryRouteDeactivate implements CanDeactivate<ArtifactListPageComponent> {
constructor(
private router: Router,
private confirmation: ConfirmationDialogService) { }
canDeactivate(
tagRepo: ArtifactListPageComponent,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | boolean {
// Confirmation before leaving config route
return new Observable((observer) => {
sessionStorage.removeItem('referenceSummary');
return observer.next(true);
});
}
}

Some files were not shown because too many files have changed in this diff Show More