mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-08 10:18:03 +02:00
add push-image button component
This commit is contained in:
parent
3586090bf6
commit
dc56b50b1e
@ -27,20 +27,21 @@
|
|||||||
"@angular/platform-browser": "^4.1.0",
|
"@angular/platform-browser": "^4.1.0",
|
||||||
"@angular/platform-browser-dynamic": "^4.1.0",
|
"@angular/platform-browser-dynamic": "^4.1.0",
|
||||||
"@angular/router": "^4.1.0",
|
"@angular/router": "^4.1.0",
|
||||||
|
"@ngx-translate/core": "^6.0.0",
|
||||||
|
"@ngx-translate/http-loader": "0.0.3",
|
||||||
"@webcomponents/custom-elements": "1.0.0-alpha.3",
|
"@webcomponents/custom-elements": "1.0.0-alpha.3",
|
||||||
"web-animations-js": "^2.2.1",
|
|
||||||
"clarity-angular": "^0.9.7",
|
"clarity-angular": "^0.9.7",
|
||||||
"clarity-icons": "^0.9.7",
|
"clarity-icons": "^0.9.7",
|
||||||
"clarity-ui": "^0.9.7",
|
"clarity-ui": "^0.9.7",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
|
"intl": "^1.2.5",
|
||||||
|
"mutationobserver-shim": "^0.3.2",
|
||||||
|
"ngx-clipboard": "^8.0.2",
|
||||||
|
"ngx-cookie": "^1.0.0",
|
||||||
"rxjs": "^5.0.1",
|
"rxjs": "^5.0.1",
|
||||||
"ts-helpers": "^1.1.1",
|
"ts-helpers": "^1.1.1",
|
||||||
"zone.js": "^0.8.4",
|
"web-animations-js": "^2.2.1",
|
||||||
"mutationobserver-shim": "^0.3.2",
|
"zone.js": "^0.8.4"
|
||||||
"@ngx-translate/core": "^6.0.0",
|
|
||||||
"@ngx-translate/http-loader": "0.0.3",
|
|
||||||
"ngx-cookie": "^1.0.0",
|
|
||||||
"intl": "^1.2.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/cli": "^1.0.0",
|
"@angular/cli": "^1.0.0",
|
||||||
|
@ -21,6 +21,7 @@ import { CONFIRMATION_DIALOG_DIRECTIVES } from './confirmation-dialog/index';
|
|||||||
import { INLINE_ALERT_DIRECTIVES } from './inline-alert/index';
|
import { INLINE_ALERT_DIRECTIVES } from './inline-alert/index';
|
||||||
import { DATETIME_PICKER_DIRECTIVES } from './datetime-picker/index';
|
import { DATETIME_PICKER_DIRECTIVES } from './datetime-picker/index';
|
||||||
import { VULNERABILITY_DIRECTIVES } from './vulnerability-scanning/index';
|
import { VULNERABILITY_DIRECTIVES } from './vulnerability-scanning/index';
|
||||||
|
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from './push-image/index';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SystemInfoService,
|
SystemInfoService,
|
||||||
@ -142,7 +143,8 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
|||||||
LIST_REPLICATION_RULE_DIRECTIVES,
|
LIST_REPLICATION_RULE_DIRECTIVES,
|
||||||
CREATE_EDIT_RULE_DIRECTIVES,
|
CREATE_EDIT_RULE_DIRECTIVES,
|
||||||
DATETIME_PICKER_DIRECTIVES,
|
DATETIME_PICKER_DIRECTIVES,
|
||||||
VULNERABILITY_DIRECTIVES
|
VULNERABILITY_DIRECTIVES,
|
||||||
|
PUSH_IMAGE_BUTTON_DIRECTIVES
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
LOG_DIRECTIVES,
|
LOG_DIRECTIVES,
|
||||||
@ -160,6 +162,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
|||||||
CREATE_EDIT_RULE_DIRECTIVES,
|
CREATE_EDIT_RULE_DIRECTIVES,
|
||||||
DATETIME_PICKER_DIRECTIVES,
|
DATETIME_PICKER_DIRECTIVES,
|
||||||
VULNERABILITY_DIRECTIVES,
|
VULNERABILITY_DIRECTIVES,
|
||||||
|
PUSH_IMAGE_BUTTON_DIRECTIVES,
|
||||||
TranslateModule
|
TranslateModule
|
||||||
],
|
],
|
||||||
providers: []
|
providers: []
|
||||||
|
@ -13,3 +13,4 @@ export * from './list-replication-rule/index';
|
|||||||
export * from './replication/index';
|
export * from './replication/index';
|
||||||
export * from './vulnerability-scanning/index';
|
export * from './vulnerability-scanning/index';
|
||||||
export * from './i18n/index';
|
export * from './i18n/index';
|
||||||
|
export * from './push-image/index';
|
48
src/ui_ng/lib/src/push-image/copy-input.component.ts
Normal file
48
src/ui_ng/lib/src/push-image/copy-input.component.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
|
||||||
|
import { COPY_INPUT_HTML } from './copy-input.html';
|
||||||
|
import { PUSH_IMAGE_STYLE } from './push-image.css';
|
||||||
|
|
||||||
|
export const enum CopyStatus {
|
||||||
|
NORMAL, SUCCESS, ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hbr-copy-input',
|
||||||
|
styles: [PUSH_IMAGE_STYLE],
|
||||||
|
template: COPY_INPUT_HTML,
|
||||||
|
|
||||||
|
providers: []
|
||||||
|
})
|
||||||
|
export class CopyInputComponent {
|
||||||
|
@Input() inputSize: number = 40;
|
||||||
|
@Input() headerTitle: string = "Copy Input";
|
||||||
|
@Input() defaultValue: string = "N/A";
|
||||||
|
|
||||||
|
state: CopyStatus = CopyStatus.NORMAL;
|
||||||
|
|
||||||
|
@Output() onCopySuccess: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
@Output() onCopyError: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
|
onSuccess($event: any): void {
|
||||||
|
this.state = CopyStatus.SUCCESS;
|
||||||
|
this.onCopySuccess.emit($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(error: any): void {
|
||||||
|
this.state = CopyStatus.ERROR;
|
||||||
|
this.onCopyError.emit(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.state = CopyStatus.NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isCopied(): boolean {
|
||||||
|
return this.state === CopyStatus.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get hasCopyError(): boolean {
|
||||||
|
return this.state === CopyStatus.ERROR;
|
||||||
|
}
|
||||||
|
}
|
15
src/ui_ng/lib/src/push-image/copy-input.html.ts
Normal file
15
src/ui_ng/lib/src/push-image/copy-input.html.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const COPY_INPUT_HTML: string = `
|
||||||
|
<div>
|
||||||
|
<div class="command-title">
|
||||||
|
{{headerTitle}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
<input type="text" class="command-input" size="{{inputSize}}" [(ngModel)]="defaultValue" #inputTarget readonly/>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<clr-icon shape="copy" [class.is-success]="isCopied" [class.is-error]="hasCopyError" class="info-tips-icon" size="24" [ngxClipboard]="inputTarget" (cbOnSuccess)="onSuccess($event)" (cbOnError)="onError($event)"></clr-icon>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
11
src/ui_ng/lib/src/push-image/index.ts
Normal file
11
src/ui_ng/lib/src/push-image/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Type } from "@angular/core";
|
||||||
|
import { PushImageButtonComponent } from './push-image.component';
|
||||||
|
import { CopyInputComponent } from './copy-input.component';
|
||||||
|
|
||||||
|
export * from "./push-image.component";
|
||||||
|
export * from './copy-input.component';
|
||||||
|
|
||||||
|
export const PUSH_IMAGE_BUTTON_DIRECTIVES: Type<any>[] = [
|
||||||
|
CopyInputComponent,
|
||||||
|
PushImageButtonComponent
|
||||||
|
];
|
64
src/ui_ng/lib/src/push-image/push-image.component.spec.ts
Normal file
64
src/ui_ng/lib/src/push-image/push-image.component.spec.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { HttpModule } from '@angular/http';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
import { PushImageButtonComponent } from './push-image.component';
|
||||||
|
import { CopyInputComponent } from './copy-input.component';
|
||||||
|
import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
|
||||||
|
|
||||||
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { click } from '../utils';
|
||||||
|
|
||||||
|
describe('PushImageButtonComponent (inline template)', () => {
|
||||||
|
let component: PushImageButtonComponent;
|
||||||
|
let fixture: ComponentFixture<PushImageButtonComponent>;
|
||||||
|
let serviceConfig: IServiceConfig;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedModule
|
||||||
|
],
|
||||||
|
declarations: [InlineAlertComponent, CopyInputComponent, PushImageButtonComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: SERVICE_CONFIG, useValue: {} }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PushImageButtonComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.projectName = "testing";
|
||||||
|
component.registryUrl = "https://testing.harbor.com"
|
||||||
|
serviceConfig = TestBed.get(SERVICE_CONFIG);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open the drop-down panel', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
let el: HTMLElement = fixture.nativeElement.querySelector('button');
|
||||||
|
expect(el).not.toBeNull();
|
||||||
|
el.click();
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
let copyInputs: HTMLInputElement[] = fixture.nativeElement.querySelectorAll('.command-input');
|
||||||
|
expect(copyInputs.length).toEqual(2);
|
||||||
|
|
||||||
|
expect(copyInputs[0].value.trim()).toEqual(`docker tag SOURCE_IMAGE[:TAG] ${component.registryUrl}/${component.projectName}/IMAGE[:TAG]`);
|
||||||
|
expect(copyInputs[1].value.trim()).toEqual(`docker push ${component.registryUrl}/${component.projectName}/IMAGE[:TAG]`);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
51
src/ui_ng/lib/src/push-image/push-image.component.ts
Normal file
51
src/ui_ng/lib/src/push-image/push-image.component.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Component, Input, ViewChild } from '@angular/core';
|
||||||
|
import { CopyInputComponent } from './copy-input.component';
|
||||||
|
import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
|
||||||
|
|
||||||
|
import { PUSH_IMAGE_STYLE } from './push-image.css';
|
||||||
|
import { PUSH_IMAGE_HTML } from './push-image.html';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hbr-push-image-button',
|
||||||
|
template: PUSH_IMAGE_HTML,
|
||||||
|
styles: [PUSH_IMAGE_STYLE],
|
||||||
|
|
||||||
|
providers: []
|
||||||
|
})
|
||||||
|
export class PushImageButtonComponent {
|
||||||
|
@Input() registryUrl: string = "unknown";
|
||||||
|
@Input() projectName: string = "unknown";
|
||||||
|
|
||||||
|
@ViewChild("tagCopy") tagCopyInput: CopyInputComponent;
|
||||||
|
@ViewChild("pushCopy") pushCopyInput: CopyInputComponent;
|
||||||
|
@ViewChild("copyAlert") copyAlert: InlineAlertComponent;
|
||||||
|
|
||||||
|
|
||||||
|
public get tagCommand(): string {
|
||||||
|
return `docker tag SOURCE_IMAGE[:TAG] ${this.registryUrl}/${this.projectName}/IMAGE[:TAG]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get pushCommand(): string {
|
||||||
|
return `docker push ${this.registryUrl}/${this.projectName}/IMAGE[:TAG]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onclick(): void {
|
||||||
|
if (this.tagCopyInput) {
|
||||||
|
this.tagCopyInput.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pushCopyInput) {
|
||||||
|
this.pushCopyInput.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.copyAlert){
|
||||||
|
this.copyAlert.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCpError($event: any): void {
|
||||||
|
if(this.copyAlert){
|
||||||
|
this.copyAlert.showInlineError("PUSH_IMAGE.COPY_ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/ui_ng/lib/src/push-image/push-image.css.ts
Normal file
42
src/ui_ng/lib/src/push-image/push-image.css.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
export const PUSH_IMAGE_STYLE: string = `
|
||||||
|
.commands-container {
|
||||||
|
min-width: 360px;
|
||||||
|
max-width: 720px;
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-override {
|
||||||
|
display: inline-block !important;
|
||||||
|
margin-top: 0px !important;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commands-section {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-tips-icon {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-tips-icon:hover {
|
||||||
|
color: #007CBB;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-title {
|
||||||
|
font-size: 14px;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-input {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host>>>.dropdown-menu {
|
||||||
|
min-width: 360px;
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
`;
|
34
src/ui_ng/lib/src/push-image/push-image.html.ts
Normal file
34
src/ui_ng/lib/src/push-image/push-image.html.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export const PUSH_IMAGE_HTML: string = `
|
||||||
|
<div>
|
||||||
|
<clr-dropdown [clrMenuPosition]="'bottom-right'">
|
||||||
|
<button class="btn btn-link" clrDropdownToggle (click)="onclick()">
|
||||||
|
{{ 'PUSH_IMAGE.TITLE' | translate | uppercase}}
|
||||||
|
<clr-icon shape="caret down"></clr-icon>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu" style="min-width:500px;">
|
||||||
|
<div class="commands-container">
|
||||||
|
<section>
|
||||||
|
<span><h5 class="h5-override">{{ 'PUSH_IMAGE.TITLE' | translate }}</h5></span>
|
||||||
|
<span>
|
||||||
|
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||||
|
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||||
|
<span class="tooltip-content">{{ 'PUSH_IMAGE.TOOLTIP' | translate }}</span>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<inline-alert #copyAlert></inline-alert>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<article class="commands-section">
|
||||||
|
<hbr-copy-input #tagCopy (onCopyError)="onCpError($event)" inputSize="50" headerTitle="{{ 'PUSH_IMAGE.TAG_COMMAND' | translate }}" defaultValue="{{tagCommand}}"></hbr-copy-input>
|
||||||
|
</article>
|
||||||
|
<article class="commands-section">
|
||||||
|
<hbr-copy-input #pushCopy (onCopyError)="onCpError($event)" inputSize="50" headerTitle="{{ 'PUSH_IMAGE.PUSH_COMMAND' | translate }}" defaultValue="{{pushCommand}}"></hbr-copy-input>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-dropdown>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -9,6 +9,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
|||||||
import { TranslatorJsonLoader } from '../i18n/local-json.loader';
|
import { TranslatorJsonLoader } from '../i18n/local-json.loader';
|
||||||
import { IServiceConfig, SERVICE_CONFIG } from '../service.config';
|
import { IServiceConfig, SERVICE_CONFIG } from '../service.config';
|
||||||
import { CookieService, CookieModule } from 'ngx-cookie';
|
import { CookieService, CookieModule } from 'ngx-cookie';
|
||||||
|
import { ClipboardModule } from 'ngx-clipboard';
|
||||||
|
|
||||||
/*export function HttpLoaderFactory(http: Http) {
|
/*export function HttpLoaderFactory(http: Http) {
|
||||||
return new TranslateHttpLoader(http, 'i18n/lang/', '-lang.json');
|
return new TranslateHttpLoader(http, 'i18n/lang/', '-lang.json');
|
||||||
@ -40,6 +41,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) {
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
HttpModule,
|
HttpModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ClipboardModule,
|
||||||
CookieModule.forRoot(),
|
CookieModule.forRoot(),
|
||||||
ClarityModule.forRoot(),
|
ClarityModule.forRoot(),
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
@ -59,6 +61,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) {
|
|||||||
HttpModule,
|
HttpModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
CookieModule,
|
CookieModule,
|
||||||
|
ClipboardModule,
|
||||||
ClarityModule,
|
ClarityModule,
|
||||||
TranslateModule
|
TranslateModule
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user