From dc56b50b1e5c7cca3cb522dc9cfec757badfdc19 Mon Sep 17 00:00:00 2001 From: Steven Zou Date: Fri, 9 Jun 2017 15:46:16 +0800 Subject: [PATCH] add push-image button component --- src/ui_ng/lib/package.json | 145 +++++++++--------- src/ui_ng/lib/src/harbor-library.module.ts | 5 +- src/ui_ng/lib/src/index.ts | 3 +- .../src/push-image/copy-input.component.ts | 48 ++++++ .../lib/src/push-image/copy-input.html.ts | 15 ++ src/ui_ng/lib/src/push-image/index.ts | 11 ++ .../push-image/push-image.component.spec.ts | 64 ++++++++ .../src/push-image/push-image.component.ts | 51 ++++++ .../lib/src/push-image/push-image.css.ts | 42 +++++ .../lib/src/push-image/push-image.html.ts | 34 ++++ src/ui_ng/lib/src/shared/shared.module.ts | 3 + 11 files changed, 347 insertions(+), 74 deletions(-) create mode 100644 src/ui_ng/lib/src/push-image/copy-input.component.ts create mode 100644 src/ui_ng/lib/src/push-image/copy-input.html.ts create mode 100644 src/ui_ng/lib/src/push-image/index.ts create mode 100644 src/ui_ng/lib/src/push-image/push-image.component.spec.ts create mode 100644 src/ui_ng/lib/src/push-image/push-image.component.ts create mode 100644 src/ui_ng/lib/src/push-image/push-image.css.ts create mode 100644 src/ui_ng/lib/src/push-image/push-image.html.ts diff --git a/src/ui_ng/lib/package.json b/src/ui_ng/lib/package.json index 32060452f..df516dae1 100644 --- a/src/ui_ng/lib/package.json +++ b/src/ui_ng/lib/package.json @@ -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" - } -} \ No newline at end of file + "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" + } +} diff --git a/src/ui_ng/lib/src/harbor-library.module.ts b/src/ui_ng/lib/src/harbor-library.module.ts index 186311cbf..27a645976 100644 --- a/src/ui_ng/lib/src/harbor-library.module.ts +++ b/src/ui_ng/lib/src/harbor-library.module.ts @@ -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: [] diff --git a/src/ui_ng/lib/src/index.ts b/src/ui_ng/lib/src/index.ts index 6b5ee7e77..904b3785b 100644 --- a/src/ui_ng/lib/src/index.ts +++ b/src/ui_ng/lib/src/index.ts @@ -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'; \ No newline at end of file +export * from './i18n/index'; +export * from './push-image/index'; \ No newline at end of file diff --git a/src/ui_ng/lib/src/push-image/copy-input.component.ts b/src/ui_ng/lib/src/push-image/copy-input.component.ts new file mode 100644 index 000000000..081a7c8f1 --- /dev/null +++ b/src/ui_ng/lib/src/push-image/copy-input.component.ts @@ -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 = new EventEmitter(); + @Output() onCopyError: EventEmitter = new EventEmitter(); + + 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; + } +} diff --git a/src/ui_ng/lib/src/push-image/copy-input.html.ts b/src/ui_ng/lib/src/push-image/copy-input.html.ts new file mode 100644 index 000000000..408345d66 --- /dev/null +++ b/src/ui_ng/lib/src/push-image/copy-input.html.ts @@ -0,0 +1,15 @@ +export const COPY_INPUT_HTML: string = ` +
+
+ {{headerTitle}} +
+
+ + + + + + +
+
+`; \ No newline at end of file diff --git a/src/ui_ng/lib/src/push-image/index.ts b/src/ui_ng/lib/src/push-image/index.ts new file mode 100644 index 000000000..4a46845ec --- /dev/null +++ b/src/ui_ng/lib/src/push-image/index.ts @@ -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[] = [ + CopyInputComponent, + PushImageButtonComponent +]; \ No newline at end of file diff --git a/src/ui_ng/lib/src/push-image/push-image.component.spec.ts b/src/ui_ng/lib/src/push-image/push-image.component.spec.ts new file mode 100644 index 000000000..9b266712b --- /dev/null +++ b/src/ui_ng/lib/src/push-image/push-image.component.spec.ts @@ -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; + 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]`); + }); + })); + +}); diff --git a/src/ui_ng/lib/src/push-image/push-image.component.ts b/src/ui_ng/lib/src/push-image/push-image.component.ts new file mode 100644 index 000000000..ab776ebc9 --- /dev/null +++ b/src/ui_ng/lib/src/push-image/push-image.component.ts @@ -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"); + } + } +} diff --git a/src/ui_ng/lib/src/push-image/push-image.css.ts b/src/ui_ng/lib/src/push-image/push-image.css.ts new file mode 100644 index 000000000..4fdca4ade --- /dev/null +++ b/src/ui_ng/lib/src/push-image/push-image.css.ts @@ -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; +} +`; \ No newline at end of file diff --git a/src/ui_ng/lib/src/push-image/push-image.html.ts b/src/ui_ng/lib/src/push-image/push-image.html.ts new file mode 100644 index 000000000..e75112f62 --- /dev/null +++ b/src/ui_ng/lib/src/push-image/push-image.html.ts @@ -0,0 +1,34 @@ +export const PUSH_IMAGE_HTML: string = ` +
+ + + + +
+`; \ No newline at end of file diff --git a/src/ui_ng/lib/src/shared/shared.module.ts b/src/ui_ng/lib/src/shared/shared.module.ts index dba8417cd..5e788e186 100644 --- a/src/ui_ng/lib/src/shared/shared.module.ts +++ b/src/ui_ng/lib/src/shared/shared.module.ts @@ -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 ],