Merge pull request #2484 from steven-zou/master

add push-image button component
This commit is contained in:
kun wang 2017-06-09 16:28:46 +08:00 committed by GitHub
commit 603cc2bd28
11 changed files with 347 additions and 74 deletions

View File

@ -1,73 +1,74 @@
{
"name": "harbor-ui",
"version": "0.1.0",
"description": "Harbor shared UI components based on Clarity and Angular4",
"scripts": {
"start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json",
"lint": "tslint \"src/**/*.ts\"",
"test": "ng test --single-run",
"test:once": "karma start karma.conf.js --single-run",
"pree2e": "webdriver-manager update",
"e2e": "protractor",
"cleanup": "rimraf dist",
"copy": "copyfiles -f README.md LICENSE AUTHORS pkg/package.json dist",
"transpile": "ngc -p tsconfig.json",
"package": "rollup -c",
"minify": "uglifyjs dist/bundles/harborui.umd.js --screw-ie8 --compress --mangle --comments --output dist/bundles/harborui.umd.min.js",
"build": "npm run cleanup && npm run transpile && npm run package && npm run minify && npm run copy"
},
"private": true,
"dependencies": {
"@angular/animations": "^4.1.0",
"@angular/common": "^4.1.0",
"@angular/compiler": "^4.1.0",
"@angular/core": "^4.1.0",
"@angular/forms": "^4.1.0",
"@angular/http": "^4.1.0",
"@angular/platform-browser": "^4.1.0",
"@angular/platform-browser-dynamic": "^4.1.0",
"@angular/router": "^4.1.0",
"@webcomponents/custom-elements": "1.0.0-alpha.3",
"web-animations-js": "^2.2.1",
"clarity-angular": "^0.9.7",
"clarity-icons": "^0.9.7",
"clarity-ui": "^0.9.7",
"core-js": "^2.4.1",
"rxjs": "^5.0.1",
"ts-helpers": "^1.1.1",
"zone.js": "^0.8.4",
"mutationobserver-shim": "^0.3.2",
"@ngx-translate/core": "^6.0.0",
"@ngx-translate/http-loader": "0.0.3",
"ngx-cookie": "^1.0.0",
"intl": "^1.2.5"
},
"devDependencies": {
"@angular/cli": "^1.0.0",
"@angular/compiler-cli": "^4.0.1",
"@types/core-js": "^0.9.41",
"@types/jasmine": "~2.2.30",
"@types/node": "^6.0.42",
"bootstrap": "4.0.0-alpha.5",
"codelyzer": "~2.0.0-beta.4",
"enhanced-resolve": "^3.0.0",
"jasmine-core": "2.4.1",
"jasmine-spec-reporter": "2.5.0",
"karma": "1.2.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-mocha-reporter": "^2.2.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-remap-istanbul": "^0.2.1",
"protractor": "^4.0.9",
"rollup": "^0.41.6",
"ts-node": "1.2.1",
"tslint": "^4.1.1",
"typescript": "~2.2.0",
"typings": "^1.4.0",
"uglify-js": "^2.8.22",
"webdriver-manager": "10.2.5",
"rimraf": "^2.6.1",
"copyfiles": "^1.2.0"
}
}
"name": "harbor-ui",
"version": "0.1.0",
"description": "Harbor shared UI components based on Clarity and Angular4",
"scripts": {
"start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json",
"lint": "tslint \"src/**/*.ts\"",
"test": "ng test --single-run",
"test:once": "karma start karma.conf.js --single-run",
"pree2e": "webdriver-manager update",
"e2e": "protractor",
"cleanup": "rimraf dist",
"copy": "copyfiles -f README.md LICENSE AUTHORS pkg/package.json dist",
"transpile": "ngc -p tsconfig.json",
"package": "rollup -c",
"minify": "uglifyjs dist/bundles/harborui.umd.js --screw-ie8 --compress --mangle --comments --output dist/bundles/harborui.umd.min.js",
"build": "npm run cleanup && npm run transpile && npm run package && npm run minify && npm run copy"
},
"private": true,
"dependencies": {
"@angular/animations": "^4.1.0",
"@angular/common": "^4.1.0",
"@angular/compiler": "^4.1.0",
"@angular/core": "^4.1.0",
"@angular/forms": "^4.1.0",
"@angular/http": "^4.1.0",
"@angular/platform-browser": "^4.1.0",
"@angular/platform-browser-dynamic": "^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",
"clarity-angular": "^0.9.7",
"clarity-icons": "^0.9.7",
"clarity-ui": "^0.9.7",
"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",
"ts-helpers": "^1.1.1",
"web-animations-js": "^2.2.1",
"zone.js": "^0.8.4"
},
"devDependencies": {
"@angular/cli": "^1.0.0",
"@angular/compiler-cli": "^4.0.1",
"@types/core-js": "^0.9.41",
"@types/jasmine": "~2.2.30",
"@types/node": "^6.0.42",
"bootstrap": "4.0.0-alpha.5",
"codelyzer": "~2.0.0-beta.4",
"enhanced-resolve": "^3.0.0",
"jasmine-core": "2.4.1",
"jasmine-spec-reporter": "2.5.0",
"karma": "1.2.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-mocha-reporter": "^2.2.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-remap-istanbul": "^0.2.1",
"protractor": "^4.0.9",
"rollup": "^0.41.6",
"ts-node": "1.2.1",
"tslint": "^4.1.1",
"typescript": "~2.2.0",
"typings": "^1.4.0",
"uglify-js": "^2.8.22",
"webdriver-manager": "10.2.5",
"rimraf": "^2.6.1",
"copyfiles": "^1.2.0"
}
}

View File

@ -21,6 +21,7 @@ import { CONFIRMATION_DIALOG_DIRECTIVES } from './confirmation-dialog/index';
import { INLINE_ALERT_DIRECTIVES } from './inline-alert/index';
import { DATETIME_PICKER_DIRECTIVES } from './datetime-picker/index';
import { VULNERABILITY_DIRECTIVES } from './vulnerability-scanning/index';
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from './push-image/index';
import {
SystemInfoService,
@ -142,7 +143,8 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
LIST_REPLICATION_RULE_DIRECTIVES,
CREATE_EDIT_RULE_DIRECTIVES,
DATETIME_PICKER_DIRECTIVES,
VULNERABILITY_DIRECTIVES
VULNERABILITY_DIRECTIVES,
PUSH_IMAGE_BUTTON_DIRECTIVES
],
exports: [
LOG_DIRECTIVES,
@ -160,6 +162,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
CREATE_EDIT_RULE_DIRECTIVES,
DATETIME_PICKER_DIRECTIVES,
VULNERABILITY_DIRECTIVES,
PUSH_IMAGE_BUTTON_DIRECTIVES,
TranslateModule
],
providers: []

View File

@ -12,4 +12,5 @@ export * from './tag/index';
export * from './list-replication-rule/index';
export * from './replication/index';
export * from './vulnerability-scanning/index';
export * from './i18n/index';
export * from './i18n/index';
export * from './push-image/index';

View 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;
}
}

View 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>
`;

View 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
];

View 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]`);
});
}));
});

View 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");
}
}
}

View 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;
}
`;

View 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>
`;

View File

@ -9,6 +9,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { TranslatorJsonLoader } from '../i18n/local-json.loader';
import { IServiceConfig, SERVICE_CONFIG } from '../service.config';
import { CookieService, CookieModule } from 'ngx-cookie';
import { ClipboardModule } from 'ngx-clipboard';
/*export function HttpLoaderFactory(http: Http) {
return new TranslateHttpLoader(http, 'i18n/lang/', '-lang.json');
@ -40,6 +41,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) {
CommonModule,
HttpModule,
FormsModule,
ClipboardModule,
CookieModule.forRoot(),
ClarityModule.forRoot(),
TranslateModule.forRoot({
@ -59,6 +61,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) {
HttpModule,
FormsModule,
CookieModule,
ClipboardModule,
ClarityModule,
TranslateModule
],