mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 02:35:17 +01:00
Improve routing and UI for artifact pages
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
86b3e47f81
commit
8bff170c89
@ -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,
|
||||
|
@ -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: [
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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"><<a (click)="backInitRepo()">{{repoName}}</a></span>
|
||||
<span *ngIf="referArtifactNameArray?.length>=1" >
|
||||
<span *ngFor="let digest of referArtifactNameArray;let i = index">
|
||||
<<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>
|
@ -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 },
|
@ -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));
|
||||
}
|
||||
updateArtifactList(res): void {
|
||||
this.artifactService.triggerUploadArtifact.next(res);
|
||||
}
|
||||
putArtifactReferenceArr(digestArray) {
|
||||
this.referArtifactNameArray = digestArray;
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -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'" />
|
||||
|
||||
<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>
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
@ -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,91 +154,123 @@ 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;
|
||||
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.artifactService.TriggerArtifactChan$.subscribe(res => {
|
||||
let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
|
||||
this.clrLoad(st);
|
||||
});
|
||||
this.lastFilteredTagName = '';
|
||||
|
||||
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;
|
||||
} else {
|
||||
data.show = false;
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
setInterval(() => this.ref.markForCheck(), 200);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
} else {
|
||||
data.show = false;
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
setInterval(() => this.ref.markForCheck(), 200);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
} else {
|
||||
data.show = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
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;
|
||||
} else {
|
||||
data.show = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
this.getImagePermissionRule(this.projectId);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
}
|
||||
|
||||
public get filterLabelPieceWidth() {
|
||||
let len = this.lastFilteredTagName.length ? this.lastFilteredTagName.length * 6 + 60 : 115;
|
||||
return len > 210 ? 210 : len;
|
||||
@ -292,88 +315,86 @@ export class ArtifactListTabComponent implements OnInit, AfterViewInit {
|
||||
|
||||
this.clrLoad(st);
|
||||
}
|
||||
|
||||
// todo
|
||||
clrDgRefresh(state: ClrDatagridStateInterface) {
|
||||
setTimeout(() => {
|
||||
this.clrLoad(state);
|
||||
});
|
||||
}
|
||||
clrLoad(state: ClrDatagridStateInterface): void {
|
||||
if (!state || !state.page) {
|
||||
return;
|
||||
}
|
||||
this.selectedRow = [];
|
||||
// Keep it for future filtering and sorting
|
||||
this.artifactList = [];
|
||||
this.loading = true;
|
||||
if (!state || !state.page) {
|
||||
return;
|
||||
}
|
||||
this.selectedRow = [];
|
||||
// Keep it for future filtering and sorting
|
||||
|
||||
let pageNumber: number = calculatePage(state);
|
||||
if (pageNumber <= 0) { pageNumber = 1; }
|
||||
let sortBy: any = '';
|
||||
if (state.sort) {
|
||||
sortBy = state.sort.by as string | ClrDatagridComparatorInterface<any>;
|
||||
sortBy = sortBy.fieldName ? sortBy.fieldName : sortBy;
|
||||
sortBy = state.sort.reverse ? `-${sortBy}` : sortBy;
|
||||
}
|
||||
this.loading = true;
|
||||
this.currentState = state;
|
||||
|
||||
// Pagination
|
||||
let params = new HttpParams();
|
||||
params = params.set('with_label', 'true');
|
||||
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 + '');
|
||||
}
|
||||
if (sortBy) {
|
||||
params = params.set('sort', sortBy);
|
||||
}
|
||||
if (state.filters && state.filters.length) {
|
||||
state.filters.forEach(item => {
|
||||
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) {
|
||||
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) => {
|
||||
if (index >= (pageNumber - 1) * this.pageSize && index < pageNumber * this.pageSize) {
|
||||
observableLists.push(this.artifactService.getArtifactFromDigest(this.projectName, this.repoName,
|
||||
child.child_digest));
|
||||
}
|
||||
});
|
||||
let pageNumber: number = calculatePage(state);
|
||||
if (pageNumber <= 0) { pageNumber = 1; }
|
||||
let sortBy: any = '';
|
||||
if (state.sort) {
|
||||
sortBy = state.sort.by as string | ClrDatagridComparatorInterface<any>;
|
||||
sortBy = sortBy.fieldName ? sortBy.fieldName : sortBy;
|
||||
sortBy = state.sort.reverse ? `-${sortBy}` : sortBy;
|
||||
}
|
||||
this.currentState = state;
|
||||
|
||||
// Pagination
|
||||
let params = new HttpParams();
|
||||
params = params.set('with_label', 'true');
|
||||
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 + '');
|
||||
}
|
||||
if (sortBy) {
|
||||
params = params.set('sort', sortBy);
|
||||
}
|
||||
if (state.filters && state.filters.length) {
|
||||
state.filters.forEach(item => {
|
||||
params = params.set(item.property, item.value);
|
||||
});
|
||||
}
|
||||
if (this.artifactDigest) {
|
||||
this.artifactService.getArtifactFromDigest(this.projectName, this.repoName, this.artifactDigest).subscribe(
|
||||
res => {
|
||||
let observableLists: Observable<Artifact>[] = [];
|
||||
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);
|
||||
});
|
||||
});
|
||||
} 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);
|
||||
}
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
}
|
||||
this.artifactList = res.body;
|
||||
this.loading = false;
|
||||
}, error => {
|
||||
// error
|
||||
this.loading = false;
|
||||
});
|
||||
);
|
||||
} else {
|
||||
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) {
|
||||
this.totalCount = parseInt(xHeader, 0);
|
||||
}
|
||||
}
|
||||
this.artifactList = res.body;
|
||||
}, error => {
|
||||
// error
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
goIntoIndexArtifact(artifact: Artifact) {
|
||||
let depth: string = '';
|
||||
if (this.depth) {
|
||||
depth = this.depth + '-' + artifact.digest;
|
||||
} else {
|
||||
depth = artifact.digest;
|
||||
}
|
||||
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);
|
||||
const linkUrl = ['harbor', 'projects', this.projectId, 'repositories', this.repoName, 'depth', depth];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 },
|
@ -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',
|
@ -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;
|
@ -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',
|
@ -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;
|
@ -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",
|
@ -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;
|
@ -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",
|
@ -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;
|
@ -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,
|
@ -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",
|
@ -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;
|
@ -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",
|
@ -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;
|
@ -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',
|
@ -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">
|
||||
<<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>
|
||||
|
@ -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;
|
||||
}
|
@ -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');
|
||||
});
|
||||
});
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
@ -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)
|
||||
);
|
@ -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', () => {
|
||||
|
@ -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";
|
||||
|
||||
|
||||
/**
|
@ -1,4 +1,5 @@
|
||||
import { Label, Tag } from "../../services";
|
||||
import { Label, Tag } from "../../../../lib/services";
|
||||
|
||||
|
||||
export class Artifact {
|
||||
id: number;
|
@ -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', () => {
|
@ -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",
|
@ -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>
|
@ -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;
|
@ -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');
|
||||
});
|
||||
});
|
@ -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));
|
||||
}
|
||||
}
|
@ -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 = {
|
@ -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.
|
@ -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 },
|
@ -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 {
|
@ -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>;
|
@ -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',
|
@ -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 ";
|
@ -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;
|
@ -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;
|
@ -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"><<a (click)="backInitRepo()">{{repoName}}</a></span>
|
||||
<span *ngIf="referArtifactNameArray.length>=2" >
|
||||
<span *ngFor="let digest of referArtifactNameArray;let i = index">
|
||||
<<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>
|
@ -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">
|
||||
<<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>
|
@ -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;
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
@ -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]);
|
||||
}
|
||||
}
|
@ -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>
|
@ -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();
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 { }
|
@ -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();
|
||||
});
|
||||
});
|
@ -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);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}));
|
||||
});
|
@ -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
Loading…
Reference in New Issue
Block a user