mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 01:27:49 +01:00
Fix validation in image name input form
Signed-off-by: 陈德 <chende@caicloud.io>
This commit is contained in:
parent
ea8fe8a2c5
commit
5063f57a1f
@ -1,13 +1,13 @@
|
||||
<form [formGroup]="imageNameForm" class="clr-form clr-form-compact" style="width:100%;">
|
||||
<form [formGroup]="imageNameForm" class="clr-form clr-form-compact">
|
||||
<div class="clr-form-control clr-row">
|
||||
<label for="project-name" class="required clr-control-label clr-col-xs-12 clr-col-md-3">{{ 'PROJECT.NAME' | translate }}</label>
|
||||
<label for="project-name" class="required clr-control-label clr-col-xs-12 clr-col-md-4">{{ 'PROJECT.NAME' | translate }}</label>
|
||||
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
|
||||
<div class="clr-input-wrapper">
|
||||
<div class="clr-input-wrapper" (mouseleave)="leaveProjectInput()">
|
||||
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='noProjectInfo'>
|
||||
<input type="text" id="project-name" (keyup)='validateProjectName()' (blur)='validateProjectName()' class="clr-input" formControlName="projectName" required minlength="2" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" />
|
||||
<input type="text" id="project-name" (keyup)='validateProjectName()' (blur)='blurProjectInput()' class="clr-input" formControlName="projectName" required minlength="2" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" />
|
||||
<span class="tooltip-content">{{noProjectInfo | translate}}</span>
|
||||
</label>
|
||||
<div class="selectBox inputWidth" [style.display]="selectedProjectList.length ? 'block' : 'none'">
|
||||
<div class="select-box" [style.display]="selectedProjectList.length ? 'block' : 'none'">
|
||||
<ul>
|
||||
<li *ngFor="let project of selectedProjectList" (click)="selectedProjectName(project?.name)">{{project?.name}}</li>
|
||||
</ul>
|
||||
@ -16,21 +16,23 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control clr-row">
|
||||
<label for="repo-name" class="required clr-control-label clr-col-xs-12 clr-col-md-3">{{ 'REPOSITORY.REPO_NAME' | translate }}</label>
|
||||
<label for="repo-name" class="required clr-control-label clr-col-xs-12 clr-col-md-4">{{ 'REPOSITORY.REPO_NAME' | translate }}</label>
|
||||
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
|
||||
<div class="clr-input-wrapper">
|
||||
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='repoName.invalid'>
|
||||
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='repoName.invalid && (repoName.dirty || repoName.touched)'>
|
||||
<input type="text" id="repo-name" class="clr-input" formControlName="repoName" required />
|
||||
<span *ngIf="repoName.invalid && (repoName.dirty || repoName.touched)" class="tooltip-content">{{ 'TOOLTIP.NONEMPTY' | translate }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control clr-row">
|
||||
<label for="tag-name" class="required clr-control-label clr-col-xs-12 clr-col-md-3">{{ 'REPOSITORY.TAG' | translate }}</label>
|
||||
<label for="tag-name" class="required clr-control-label clr-col-xs-12 clr-col-md-4">{{ 'REPOSITORY.TAG' | translate }}</label>
|
||||
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
|
||||
<div class="clr-input-wrapper">
|
||||
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='tagName.invalid'>
|
||||
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='tagName.invalid && (tagName.dirty || tagName.touched)'>
|
||||
<input type="text" id="tag-name" class="clr-input" formControlName="tagName" required />
|
||||
<span *ngIf="repoName.invalid && (repoName.dirty || repoName.touched)" class="tooltip-content">{{ 'TOOLTIP.NONEMPTY' | translate }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,27 +1,31 @@
|
||||
.selectBox {
|
||||
.clr-form {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.select-box {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border: 1px solid #ccc;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0, 0, 0, .15);
|
||||
border-right-width: 2px;
|
||||
border-bottom-width: 2px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.selectBox ul li {
|
||||
list-style: none;
|
||||
padding: 3px 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
ul {
|
||||
li {
|
||||
list-style: none;
|
||||
padding: 3px 20px;
|
||||
cursor: pointer;
|
||||
|
||||
.selectBox ul li:hover {
|
||||
color: #262626;
|
||||
background-image: linear-gradient(180deg, #f5f5f5 0, #e8e8e8);
|
||||
background-repeat: repeat-x;
|
||||
&:hover {
|
||||
color: #262626;
|
||||
background-image: linear-gradient(180deg, #f5f5f5 0, #e8e8e8);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clr-input-wrapper {
|
||||
@ -31,16 +35,18 @@
|
||||
|
||||
.wrap-label {
|
||||
display: block;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.wrap-label input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label.required:after {
|
||||
content: '*';
|
||||
font-size: .58479532rem;
|
||||
line-height: .5rem;
|
||||
color: #c92100;
|
||||
margin-left: .25rem;
|
||||
label.required {
|
||||
&:after {
|
||||
content: '*';
|
||||
font-size: .58479532rem;
|
||||
line-height: .5rem;
|
||||
color: #c92100;
|
||||
margin-left: .25rem;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { ImageNameInputComponent } from "../image-name-input/image-name-input.component";
|
||||
|
||||
import { ErrorHandler } from "../error-handler/error-handler";
|
||||
import { ProjectDefaultService, ProjectService } from "../service/index";
|
||||
import { ChannelService } from "../channel/index";
|
||||
import { Project } from "../project-policy-config/project";
|
||||
import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
|
||||
|
||||
describe("ImageNameInputComponent (inline template)", () => {
|
||||
let comp: ImageNameInputComponent;
|
||||
let fixture: ComponentFixture<ImageNameInputComponent>;
|
||||
let spy: jasmine.Spy;
|
||||
|
||||
let mockProjects: Project[] = [
|
||||
{
|
||||
"project_id": 1,
|
||||
"name": "project_01",
|
||||
"creation_time": "",
|
||||
},
|
||||
{
|
||||
"project_id": 2,
|
||||
"name": "project_02",
|
||||
"creation_time": "",
|
||||
}
|
||||
];
|
||||
|
||||
let config: IServiceConfig = {
|
||||
projectBaseEndpoint: "/api/projects/testing"
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
ImageNameInputComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
ChannelService,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: ProjectService, useClass: ProjectDefaultService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ImageNameInputComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
let projectService: ProjectService;
|
||||
projectService = fixture.debugElement.injector.get(ProjectService);
|
||||
spy = spyOn(projectService, "listProjects").and.returnValues(Promise.resolve(mockProjects));
|
||||
});
|
||||
|
||||
it("should load data", async(() => {
|
||||
expect(spy.calls.any).toBeTruthy();
|
||||
}));
|
||||
});
|
@ -1,11 +1,10 @@
|
||||
import {Component, OnDestroy, OnInit} from "@angular/core";
|
||||
import {Project} from "../project-policy-config/project";
|
||||
import {Subject} from "rxjs/index";
|
||||
import {debounceTime, distinctUntilChanged} from "rxjs/operators";
|
||||
import {toPromise} from "../utils";
|
||||
import {ProjectService} from "../service/project.service";
|
||||
import {AbstractControl, FormBuilder, FormGroup, Validators} from "@angular/forms";
|
||||
import {ErrorHandler} from "../error-handler/error-handler";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Project } from "../project-policy-config/project";
|
||||
import { Observable, Subject } from "rxjs/index";
|
||||
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
|
||||
import { ProjectService } from "../service/project.service";
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { ErrorHandler } from "../error-handler/error-handler";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-image-name-input",
|
||||
@ -31,18 +30,18 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
ngOnInit(): void {
|
||||
this.proNameChecker
|
||||
.pipe(debounceTime(500))
|
||||
.pipe(debounceTime(200))
|
||||
.pipe(distinctUntilChanged())
|
||||
.subscribe((resp: string) => {
|
||||
let name = this.imageNameForm.controls["projectName"].value;
|
||||
.subscribe((name: string) => {
|
||||
this.noProjectInfo = "";
|
||||
this.selectedProjectList = [];
|
||||
toPromise<Project[]>(this.proService.listProjects(name, undefined))
|
||||
.then((res: any) => {
|
||||
if (res) {
|
||||
this.selectedProjectList = res.slice(0, 10);
|
||||
const prolist: any = this.proService.listProjects(name, undefined);
|
||||
if (prolist.subscribe) {
|
||||
prolist.subscribe(response => {
|
||||
if (response) {
|
||||
this.selectedProjectList = response.slice(0, 10);
|
||||
// if input project name exist in the project list
|
||||
let exist = res.find((data: any) => data.name === name);
|
||||
let exist = response.find((data: any) => data.name === name);
|
||||
if (!exist) {
|
||||
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
|
||||
} else {
|
||||
@ -51,11 +50,13 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
}, (error: any) => {
|
||||
this.errorHandler.error(error);
|
||||
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
|
||||
});
|
||||
} else {
|
||||
this.errorHandler.error("not Observable type");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -86,9 +87,17 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
blurProjectInput(): void {
|
||||
this.validateProjectName();
|
||||
}
|
||||
|
||||
leaveProjectInput(): void {
|
||||
this.selectedProjectList = [];
|
||||
}
|
||||
|
||||
selectedProjectName(projectName: string) {
|
||||
this.imageNameForm.controls["projectName"].setValue(projectName);
|
||||
this.selectedProjectList = [];
|
||||
this.noProjectInfo = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { ImageNameInputComponent } from "../image-name-input/image-name-input.component";
|
||||
import { RepositoryGridviewComponent } from './repository-gridview.component';
|
||||
import { TagComponent } from '../tag/tag.component';
|
||||
import { FilterComponent } from '../filter/filter.component';
|
||||
@ -21,8 +22,9 @@ import { HBR_GRIDVIEW_DIRECTIVES } from '../gridview/index';
|
||||
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
|
||||
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
import {LabelPieceComponent} from "../label-piece/label-piece.component";
|
||||
import {OperationService} from "../operation/operation.service";
|
||||
import { LabelPieceComponent } from "../label-piece/label-piece.component";
|
||||
import { OperationService } from "../operation/operation.service";
|
||||
import {ProjectDefaultService, ProjectService, RetagDefaultService, RetagService} from "../service";
|
||||
|
||||
describe('RepositoryComponentGridview (inline template)', () => {
|
||||
|
||||
@ -104,6 +106,7 @@ describe('RepositoryComponentGridview (inline template)', () => {
|
||||
TagComponent,
|
||||
LabelPieceComponent,
|
||||
ConfirmationDialogComponent,
|
||||
ImageNameInputComponent,
|
||||
FilterComponent,
|
||||
VULNERABILITY_DIRECTIVES,
|
||||
PUSH_IMAGE_BUTTON_DIRECTIVES,
|
||||
@ -116,6 +119,8 @@ describe('RepositoryComponentGridview (inline template)', () => {
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||
{ provide: TagService, useClass: TagDefaultService },
|
||||
{ provide: ProjectService, useClass: ProjectDefaultService },
|
||||
{ provide: RetagService, useClass: RetagDefaultService },
|
||||
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
|
||||
{ provide: OperationService }
|
||||
]
|
||||
|
@ -5,6 +5,7 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { ImageNameInputComponent } from "../image-name-input/image-name-input.component";
|
||||
import { RepositoryComponent } from './repository.component';
|
||||
import { RepositoryGridviewComponent } from '../repository-gridview/repository-gridview.component';
|
||||
import { GridViewComponent } from '../gridview/grid-view.component';
|
||||
@ -17,15 +18,16 @@ import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
|
||||
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import {Repository, RepositoryItem, Tag, SystemInfo, Label} from '../service/interface';
|
||||
import { Repository, RepositoryItem, Tag, SystemInfo, Label } from '../service/interface';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
|
||||
import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service';
|
||||
import { TagService, TagDefaultService } from '../service/tag.service';
|
||||
import { ChannelService } from '../channel/index';
|
||||
import {LabelPieceComponent} from "../label-piece/label-piece.component";
|
||||
import {LabelDefaultService, LabelService} from "../service/label.service";
|
||||
import {OperationService} from "../operation/operation.service";
|
||||
import { LabelPieceComponent } from "../label-piece/label-piece.component";
|
||||
import { LabelDefaultService, LabelService } from "../service/label.service";
|
||||
import { OperationService } from "../operation/operation.service";
|
||||
import { ProjectDefaultService, ProjectService, RetagDefaultService, RetagService } from "../service";
|
||||
|
||||
|
||||
class RouterStub {
|
||||
@ -159,6 +161,7 @@ describe('RepositoryComponent (inline template)', () => {
|
||||
GridViewComponent,
|
||||
RepositoryGridviewComponent,
|
||||
ConfirmationDialogComponent,
|
||||
ImageNameInputComponent,
|
||||
FilterComponent,
|
||||
TagComponent,
|
||||
LabelPieceComponent,
|
||||
@ -173,6 +176,8 @@ describe('RepositoryComponent (inline template)', () => {
|
||||
{ provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
|
||||
{ provide: TagService, useClass: TagDefaultService },
|
||||
{ provide: ProjectService, useClass: ProjectDefaultService },
|
||||
{ provide: RetagService, useClass: RetagDefaultService },
|
||||
{ provide: LabelService, useClass: LabelDefaultService},
|
||||
{ provide: ChannelService},
|
||||
{ provide: OperationService }
|
||||
|
@ -1,15 +1,15 @@
|
||||
export * from './interface';
|
||||
export * from './system-info.service';
|
||||
export * from './access-log.service';
|
||||
export * from './endpoint.service';
|
||||
export * from './replication.service';
|
||||
export * from './repository.service';
|
||||
export * from './tag.service';
|
||||
export * from './RequestQueryParams';
|
||||
export * from './scanning.service';
|
||||
export * from './configuration.service';
|
||||
export * from './job-log.service';
|
||||
export * from './project.service';
|
||||
export * from './label.service';
|
||||
export * from './helm-chart.service';
|
||||
export * from './retag.service';
|
||||
export * from "./interface";
|
||||
export * from "./system-info.service";
|
||||
export * from "./access-log.service";
|
||||
export * from "./endpoint.service";
|
||||
export * from "./replication.service";
|
||||
export * from "./repository.service";
|
||||
export * from "./tag.service";
|
||||
export * from "./RequestQueryParams";
|
||||
export * from "./scanning.service";
|
||||
export * from "./configuration.service";
|
||||
export * from "./job-log.service";
|
||||
export * from "./project.service";
|
||||
export * from "./label.service";
|
||||
export * from "./helm-chart.service";
|
||||
export * from "./retag.service";
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Observable } from "rxjs";
|
||||
import { Http } from "@angular/http";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable } from "@angular/core";
|
||||
import { RetagRequest } from "./interface";
|
||||
import { HTTP_JSON_OPTIONS } from "../utils";
|
||||
import { catchError } from "rxjs/operators";
|
||||
import { throwError as observableThrowError } from "rxjs/index";
|
||||
|
||||
/**
|
||||
* Define the service methods to perform images retag.
|
||||
@ -17,11 +19,11 @@ export abstract class RetagService {
|
||||
*
|
||||
* @abstract
|
||||
* param {RetagRequest} request
|
||||
* returns {(Observable<any> | Promise<any> | any)}
|
||||
* returns {Observable<any>}
|
||||
*
|
||||
* @memberOf RetagService
|
||||
*/
|
||||
abstract retag(request: RetagRequest): Observable<any> | Promise<any> | any;
|
||||
abstract retag(request: RetagRequest): Observable<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,7 +41,7 @@ export class RetagDefaultService extends RetagService {
|
||||
super();
|
||||
}
|
||||
|
||||
retag(request: RetagRequest): Observable<any> | Promise<any> | any {
|
||||
retag(request: RetagRequest): Observable<any> {
|
||||
return this.http
|
||||
.post(`/api/repositories/${request.targetProject}/${request.targetRepo}/tags`,
|
||||
{
|
||||
@ -48,8 +50,6 @@ export class RetagDefaultService extends RetagService {
|
||||
"override": request.override
|
||||
},
|
||||
HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
.then(response => response.status)
|
||||
.catch(error => Promise.reject(error));
|
||||
};
|
||||
}
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,13 @@
|
||||
</clr-modal>
|
||||
<clr-modal class="hidden-tag" [(clrModalOpen)]="retagDialogOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="retagDialogClosable">
|
||||
<h3 class="modal-title">{{ 'REPOSITORY.RETAG' | translate }}</h3>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body retag-modal-body">
|
||||
<div class="row col-md-12">
|
||||
<hbr-image-name-input #imageNameInput style="width:100%;"></hbr-image-name-input>
|
||||
<hbr-image-name-input #imageNameInput></hbr-image-name-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" [disabled]="imageNameInput.projectName.invalid||imageNameInput.repoName.invalid||imageNameInput.tagName.invalid" class="btn btn-primary" (click)="onRetag()">{{'BUTTON.CONFIRM' | translate}}</button>
|
||||
<button type="button" [disabled]="imageNameInput.projectName.invalid||imageNameInput.repoName.invalid||imageNameInput.tagName.invalid||imageNameInput.noProjectInfo!=''" class="btn btn-primary" (click)="onRetag()">{{'BUTTON.CONFIRM' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<div class="row" style="position:relative;">
|
||||
@ -69,7 +69,7 @@
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length==1)" (click)="retag(selectedRow)"><clr-icon shape="copy" size="16"></clr-icon> {{'REPOSITORY.RETAG' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length===1)" (click)="retag(selectedRow)"><clr-icon shape="copy" size="16"></clr-icon> {{'REPOSITORY.RETAG' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasProjectAdminRole" (click)="deleteTags(selectedRow)" [disabled]="!selectedRow.length"><clr-icon shape="times" size="16"></clr-icon> {{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column style="width: 120px;" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
|
@ -198,4 +198,14 @@
|
||||
top: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.retag-modal-body {
|
||||
overflow-y: hidden;
|
||||
min-height: 172px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
hbr-image-name-input {
|
||||
width: 100%;
|
||||
}
|
@ -1,26 +1,29 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, async } from "@angular/core/testing";
|
||||
import { DebugElement } from "@angular/core";
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { TagComponent } from './tag.component';
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
|
||||
import { ImageNameInputComponent } from "../image-name-input/image-name-input.component";
|
||||
import { TagComponent } from "./tag.component";
|
||||
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import {Label, Tag} from '../service/interface';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { TagService, TagDefaultService, ScanningResultService, ScanningResultDefaultService } from '../service/index';
|
||||
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
|
||||
import { FILTER_DIRECTIVES } from '../filter/index';
|
||||
import { ErrorHandler } from "../error-handler/error-handler";
|
||||
import { Label, Tag } from "../service/interface";
|
||||
import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
|
||||
import {
|
||||
TagService, TagDefaultService, ScanningResultService, ScanningResultDefaultService,
|
||||
RetagService, RetagDefaultService, ProjectService, ProjectDefaultService
|
||||
} from "../service/index";
|
||||
import { VULNERABILITY_DIRECTIVES } from "../vulnerability-scanning/index";
|
||||
import { FILTER_DIRECTIVES } from "../filter/index";
|
||||
import { ChannelService } from "../channel/index";
|
||||
|
||||
import { ChannelService } from '../channel/index';
|
||||
import { JobLogViewerComponent } from "../job-log-viewer/index";
|
||||
import { CopyInputComponent } from "../push-image/copy-input.component";
|
||||
import { LabelPieceComponent } from "../label-piece/label-piece.component";
|
||||
import { LabelDefaultService, LabelService } from "../service/label.service";
|
||||
import { OperationService } from "../operation/operation.service";
|
||||
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
import {CopyInputComponent} from "../push-image/copy-input.component";
|
||||
import {LabelPieceComponent} from "../label-piece/label-piece.component";
|
||||
import {LabelDefaultService, LabelService} from "../service/label.service";
|
||||
import {OperationService} from "../operation/operation.service";
|
||||
|
||||
describe('TagComponent (inline template)', () => {
|
||||
describe("TagComponent (inline template)", () => {
|
||||
|
||||
let comp: TagComponent;
|
||||
let fixture: ComponentFixture<TagComponent>;
|
||||
@ -90,7 +93,7 @@ describe('TagComponent (inline template)', () => {
|
||||
];
|
||||
|
||||
let config: IServiceConfig = {
|
||||
repositoryBaseEndpoint: '/api/repositories/testing'
|
||||
repositoryBaseEndpoint: "/api/repositories/testing"
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
@ -102,18 +105,21 @@ describe('TagComponent (inline template)', () => {
|
||||
TagComponent,
|
||||
LabelPieceComponent,
|
||||
ConfirmationDialogComponent,
|
||||
ImageNameInputComponent,
|
||||
VULNERABILITY_DIRECTIVES,
|
||||
FILTER_DIRECTIVES,
|
||||
JobLogViewerComponent,
|
||||
CopyInputComponent
|
||||
CopyInputComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
ChannelService,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: TagService, useClass: TagDefaultService },
|
||||
{ provide: ProjectService, useClass: ProjectDefaultService },
|
||||
{ provide: RetagService, useClass: RetagDefaultService },
|
||||
{ provide: ScanningResultService, useClass: ScanningResultDefaultService },
|
||||
{provide: LabelService, useClass: LabelDefaultService},
|
||||
{ provide: LabelService, useClass: LabelDefaultService },
|
||||
{ provide: OperationService }
|
||||
]
|
||||
});
|
||||
@ -124,10 +130,10 @@ describe('TagComponent (inline template)', () => {
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
comp.projectId = 1;
|
||||
comp.repoName = 'library/nginx';
|
||||
comp.repoName = "library/nginx";
|
||||
comp.hasProjectAdminRole = true;
|
||||
comp.hasSignedIn = true;
|
||||
comp.registryUrl = 'http://registry.testing.com';
|
||||
comp.registryUrl = "http://registry.testing.com";
|
||||
comp.withNotary = false;
|
||||
|
||||
|
||||
@ -135,31 +141,31 @@ describe('TagComponent (inline template)', () => {
|
||||
|
||||
|
||||
tagService = fixture.debugElement.injector.get(TagService);
|
||||
spy = spyOn(tagService, 'getTags').and.returnValues(Promise.resolve(mockTags));
|
||||
spy = spyOn(tagService, "getTags").and.returnValues(Promise.resolve(mockTags));
|
||||
|
||||
labelService = fixture.debugElement.injector.get(LabelService);
|
||||
|
||||
spyLabels = spyOn(labelService, 'getGLabels').and.returnValues(Promise.resolve(mockLabels));
|
||||
spyLabels1 = spyOn(labelService, 'getPLabels').and.returnValues(Promise.resolve(mockLabels1));
|
||||
spyLabels = spyOn(labelService, "getGLabels").and.returnValues(Promise.resolve(mockLabels));
|
||||
spyLabels1 = spyOn(labelService, "getPLabels").and.returnValues(Promise.resolve(mockLabels1));
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should load data', async(() => {
|
||||
it("should load data", async(() => {
|
||||
expect(spy.calls.any).toBeTruthy();
|
||||
}));
|
||||
|
||||
// fail after upgrade to angular 6.
|
||||
xit('should load and render data', async(() => {
|
||||
xit("should load and render data", async(() => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let de: DebugElement = fixture.debugElement.query(del => del.classes['datagrid-cell']);
|
||||
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('1.11.5');
|
||||
expect(el.textContent.trim()).toEqual("1.11.5");
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -129,10 +129,10 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
};
|
||||
filterOneLabel: Label = this.initFilter;
|
||||
|
||||
@ViewChild('confirmationDialog')
|
||||
@ViewChild("confirmationDialog")
|
||||
confirmationDialog: ConfirmationDialogComponent;
|
||||
|
||||
@ViewChild('imageNameInput')
|
||||
@ViewChild("imageNameInput")
|
||||
imageNameInput: ImageNameInputComponent;
|
||||
|
||||
@ViewChild("digestTarget") textInput: ElementRef;
|
||||
@ -574,20 +574,24 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
retag(tags: Tag[]) {
|
||||
this.retagDialogOpened = true;
|
||||
this.retagSrcImage = this.repoName + ":" + tags[0].digest;
|
||||
if (tags && tags.length) {
|
||||
this.retagDialogOpened = true;
|
||||
this.retagSrcImage = this.repoName + ":" + tags[0].digest;
|
||||
} else {
|
||||
this.errorHandler.error("One tag should be selected before retag.");
|
||||
}
|
||||
}
|
||||
|
||||
onRetag() {
|
||||
this.retagDialogOpened = false;
|
||||
toPromise<any>(this.retagService.retag({
|
||||
this.retagService.retag({
|
||||
targetProject: this.imageNameInput.projectName.value,
|
||||
targetRepo: this.imageNameInput.repoName.value,
|
||||
targetTag: this.imageNameInput.tagName.value,
|
||||
srcImage: this.retagSrcImage,
|
||||
override: true
|
||||
})).then(rsp => {
|
||||
}).catch(error => {
|
||||
}).subscribe(response => {
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
@ -67,7 +67,9 @@
|
||||
"EMAIL_EXISTING": "Email address already exists.",
|
||||
"USER_EXISTING": "Username is already in use.",
|
||||
"RULE_USER_EXISTING": "Name is already in use.",
|
||||
"EMPTY": "Name is required"
|
||||
"EMPTY": "Name is required",
|
||||
"NONEMPTY": "Can't be empty",
|
||||
"REPO_TOOLTIP": "Users can not do any operations to the images in this mode."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Enter current password",
|
||||
@ -476,7 +478,7 @@
|
||||
"ACTION": "ACTION",
|
||||
"DEPLOY": "DEPLOY",
|
||||
"ADDITIONAL_INFO": "Add Additional Info",
|
||||
"TARGET_PROJECT": "Target Project"
|
||||
"REPO_NAME": "Repository"
|
||||
},
|
||||
"HELM_CHART": {
|
||||
"HELMCHARTS": "Charts",
|
||||
|
@ -68,6 +68,7 @@
|
||||
"USER_EXISTING": "Ese nombre de usuario ya existe.",
|
||||
"RULE_USER_EXISTING": "Name is already in use.",
|
||||
"EMPTY": "Name is required",
|
||||
"NONEMPTY": "Can't be empty",
|
||||
"REPO_TOOLTIP": "Users can not do any operations to the images in this mode."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
@ -475,7 +476,7 @@
|
||||
"ACTION": "ACTION",
|
||||
"DEPLOY": "DEPLOY",
|
||||
"ADDITIONAL_INFO": "Add Additional Info",
|
||||
"TARGET_PROJECT": "Target Project"
|
||||
"REPO_NAME": "Repository"
|
||||
},
|
||||
"HELM_CHART": {
|
||||
"HELMCHARTS": "Charts",
|
||||
|
@ -55,6 +55,7 @@
|
||||
"PORT_REQUIRED": "Le champ est obligatoire et doit être un numéro de port valide.",
|
||||
"EMAIL_EXISTING": "L'adresse e-mail existe déjà.",
|
||||
"USER_EXISTING": "Le nom d'utilisateur est déjà utilisé.",
|
||||
"NONEMPTY": "Can't be empty",
|
||||
"REPO_TOOLTIP": "Users can not do any operations to the images in this mode."
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
@ -453,7 +454,7 @@
|
||||
"ACTION": "ACTION",
|
||||
"DEPLOY": "DEPLOY",
|
||||
"ADDITIONAL_INFO": "Add Additional Info",
|
||||
"TARGET_PROJECT": "Projet Cible"
|
||||
"REPO_NAME": "Repository"
|
||||
},
|
||||
"HELM_CHART": {
|
||||
"HELMCHARTS": "Charts",
|
||||
|
@ -67,7 +67,8 @@
|
||||
"EMAIL_EXISTING": "邮件地址已经存在。",
|
||||
"USER_EXISTING": "用户名已经存在。",
|
||||
"RULE_USER_EXISTING": "名称已经存在。",
|
||||
"EMPTY": "名称为必填项"
|
||||
"EMPTY": "名称为必填项",
|
||||
"NONEMPTY": "不能为空"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "输入当前密码",
|
||||
@ -475,7 +476,7 @@
|
||||
"ACTION": "操作",
|
||||
"DEPLOY": "部署",
|
||||
"ADDITIONAL_INFO": "添加信息",
|
||||
"TARGET_PROJECT": "目标项目"
|
||||
"REPO_NAME": "镜像仓库"
|
||||
},
|
||||
"HELM_CHART": {
|
||||
"HELMCHARTS": "Charts",
|
||||
|
Loading…
Reference in New Issue
Block a user