Merge pull request #4963 from ninjadq/fix_tslint_warning

Fix tslint warning
This commit is contained in:
Steven Zou 2018-05-15 18:41:15 +08:00 committed by GitHub
commit 1f7a4f18e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
236 changed files with 5401 additions and 4753 deletions

View File

@ -208,7 +208,7 @@ func (ctl *DefaultController) Replicate(policyID int64, metadata ...map[string]i
// prepare candidates for replication // prepare candidates for replication
candidates := getCandidates(&policy, ctl.sourcer, metadata...) candidates := getCandidates(&policy, ctl.sourcer, metadata...)
if len(candidates) == 0 { if len(candidates) == 0 {
log.Debugf("replicaton candidates are null, no further action needed") log.Debugf("replication candidates are null, no further action needed")
} }
targets := []*common_models.RepTarget{} targets := []*common_models.RepTarget{}

View File

@ -1 +1 @@
export * from './src/index'; export * from './src/index';

View File

@ -1,6 +1,6 @@
{ {
"name": "harbor-ui", "name": "harbor-ui",
"version": "0.7.18-dev.6", "version": "0.7.18-dev.8",
"description": "Harbor shared UI components based on Clarity and Angular4", "description": "Harbor shared UI components based on Clarity and Angular4",
"author": "VMware", "author": "VMware",
"module": "index.js", "module": "index.js",

View File

@ -13,16 +13,17 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
// tslint:disable-next-line:no-unused-variable
import { Observable } from "rxjs/Observable"; import { Observable } from "rxjs/Observable";
@Injectable() @Injectable()
export class ChannelService { export class ChannelService {
//Declare for publishing scan event // Declare for publishing scan event
scanCommandSource = new Subject<string>(); scanCommandSource = new Subject<string>();
scanCommand$ = this.scanCommandSource.asObservable(); scanCommand$ = this.scanCommandSource.asObservable();
publishScanEvent(tagId: string): void { publishScanEvent(tagId: string): void {
this.scanCommandSource.next(tagId); this.scanCommandSource.next(tagId);
} }
} }

View File

@ -1 +1 @@
export * from './channel.service'; export * from './channel.service';

View File

@ -119,4 +119,4 @@ export class Configuration {
}, true); }, true);
this.read_only = new BoolValueItem(false, true); this.read_only = new BoolValueItem(false, true);
} }
} }

View File

@ -16,4 +16,4 @@ export const CONFIGURATION_DIRECTIVES: Type<any>[] = [
SystemSettingsComponent, SystemSettingsComponent,
VulnerabilityConfigComponent, VulnerabilityConfigComponent,
RegistryConfigComponent RegistryConfigComponent
]; ];

View File

@ -114,4 +114,4 @@ describe('RegistryConfigComponent (inline template)', () => {
expect(saveSpy.calls.any).toBeTruthy(); expect(saveSpy.calls.any).toBeTruthy();
})); }));
}); });

View File

@ -1,7 +1,11 @@
import { Component, OnInit, EventEmitter, Output, ViewChild, Input } from '@angular/core'; import { Component, OnInit, ViewChild, Input } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Configuration, ComplexValueItem } from './config'; import { ConfirmationState, ConfirmationTargets } from '../shared/shared.const';
import { ConfigurationService, SystemInfoService, SystemInfo, ClairDBStatus } from '../service/index'; import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
import { ConfigurationService, SystemInfoService, SystemInfo } from '../service/index';
import { import {
toPromise, toPromise,
compareValue, compareValue,
@ -9,17 +13,8 @@ import {
clone clone
} from '../utils'; } from '../utils';
import { ErrorHandler } from '../error-handler/index'; import { ErrorHandler } from '../error-handler/index';
import { import { SystemSettingsComponent, VulnerabilityConfigComponent } from './index';
SystemSettingsComponent, import { Configuration } from './config';
VulnerabilityConfigComponent
} from './index';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/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';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
selector: 'hbr-registry-config', selector: 'hbr-registry-config',
@ -62,7 +57,7 @@ export class RegistryConfigComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.loadSystemInfo(); this.loadSystemInfo();
//Initialize // Initialize
this.load(); this.load();
} }
@ -77,14 +72,14 @@ export class RegistryConfigComponent implements OnInit {
return !isEmptyObject(this.getChanges()); return !isEmptyObject(this.getChanges());
} }
//Get system info // Get system info
loadSystemInfo(): void { loadSystemInfo(): void {
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo()) toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then((info: SystemInfo) => this.systemInfo = info) .then((info: SystemInfo) => this.systemInfo = info)
.catch(error => this.errorHandler.error(error)); .catch(error => this.errorHandler.error(error));
} }
//Load configurations // Load configurations
load(): void { load(): void {
this.onGoing = true; this.onGoing = true;
toPromise<Configuration>(this.configService.getConfigurations()) toPromise<Configuration>(this.configService.getConfigurations())
@ -99,12 +94,12 @@ export class RegistryConfigComponent implements OnInit {
}); });
} }
//Save configuration changes // Save configuration changes
save(): void { save(): void {
let changes: { [key: string]: any | any[] } = this.getChanges(); let changes: { [key: string]: any | any[] } = this.getChanges();
if (isEmptyObject(changes)) { if (isEmptyObject(changes)) {
//Guard code, do nothing // Guard code, do nothing
return; return;
} }
@ -116,10 +111,10 @@ export class RegistryConfigComponent implements OnInit {
this.translate.get("CONFIG.SAVE_SUCCESS").subscribe((res: string) => { this.translate.get("CONFIG.SAVE_SUCCESS").subscribe((res: string) => {
this.errorHandler.info(res); this.errorHandler.info(res);
}); });
//Reload to fetch all the updates // Reload to fetch all the updates
this.load(); this.load();
//Reload all system info // Reload all system info
//this.loadSystemInfo(); // this.loadSystemInfo();
}) })
.catch(error => { .catch(error => {
this.onGoing = false; this.onGoing = false;
@ -127,7 +122,7 @@ export class RegistryConfigComponent implements OnInit {
}); });
} }
//Cancel the changes if have // Cancel the changes if have
cancel(): void { cancel(): void {
let msg = new ConfirmationMessage( let msg = new ConfirmationMessage(
"CONFIG.CONFIRM_TITLE", "CONFIG.CONFIRM_TITLE",
@ -139,7 +134,7 @@ export class RegistryConfigComponent implements OnInit {
this.confirmationDlg.open(msg); this.confirmationDlg.open(msg);
} }
//Confirm cancel // Confirm cancel
confirmCancel(ack: ConfirmationAcknowledgement): void { confirmCancel(ack: ConfirmationAcknowledgement): void {
if (ack && ack.source === ConfirmationTargets.CONFIG && if (ack && ack.source === ConfirmationTargets.CONFIG &&
ack.state === ConfirmationState.CONFIRMED) { ack.state === ConfirmationState.CONFIRMED) {
@ -148,9 +143,9 @@ export class RegistryConfigComponent implements OnInit {
} }
reset(): void { reset(): void {
//Reset to the values of copy // Reset to the values of copy
let changes: { [key: string]: any | any[] } = this.getChanges(); let changes: { [key: string]: any | any[] } = this.getChanges();
for (let prop in changes) { for (let prop of Object.keys(changes)) {
this.config[prop] = clone(this.configCopy[prop]); this.config[prop] = clone(this.configCopy[prop]);
} }
} }
@ -161,17 +156,17 @@ export class RegistryConfigComponent implements OnInit {
return changes; return changes;
} }
for (let prop in this.config) { for (let prop of Object.keys(this.config)) {
let field = this.configCopy[prop]; let field = this.configCopy[prop];
if (field && field.editable) { if (field && field.editable) {
if (!compareValue(field.value, this.config[prop].value)) { if (!compareValue(field.value, this.config[prop].value)) {
changes[prop] = this.config[prop].value; changes[prop] = this.config[prop].value;
//Number // Number
if (typeof field.value === "number") { if (typeof field.value === "number") {
changes[prop] = +changes[prop]; changes[prop] = +changes[prop];
} }
//Trim string value // Trim string value
if (typeof field.value === "string") { if (typeof field.value === "string") {
changes[prop] = ('' + changes[prop]).trim(); changes[prop] = ('' + changes[prop]).trim();
} }
@ -181,4 +176,4 @@ export class RegistryConfigComponent implements OnInit {
return changes; return changes;
} }
} }

View File

@ -21,7 +21,7 @@ export class ReplicationConfigComponent {
this.configChange.emit(this.config); this.configChange.emit(this.config);
} }
@Input() showSubTitle: boolean = false @Input() showSubTitle: boolean = false;
@ViewChild("replicationConfigFrom") replicationConfigForm: NgForm; @ViewChild("replicationConfigFrom") replicationConfigForm: NgForm;
@ -34,4 +34,4 @@ export class ReplicationConfigComponent {
get isValid(): boolean { get isValid(): boolean {
return this.replicationConfigForm && this.replicationConfigForm.valid; return this.replicationConfigForm && this.replicationConfigForm.valid;
} }
} }

View File

@ -53,4 +53,4 @@ export class SystemSettingsComponent {
this.downloadLink = this.configInfo.systemInfoEndpoint + "/getcert"; this.downloadLink = this.configInfo.systemInfoEndpoint + "/getcert";
} }
} }
} }

View File

@ -10,7 +10,7 @@ import {
import { ErrorHandler } from '../../error-handler/index'; import { ErrorHandler } from '../../error-handler/index';
import { toPromise } from '../../utils'; import { toPromise } from '../../utils';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ClairDBStatus, ClairDetail } from '../../service/interface'; import { ClairDetail } from '../../service/interface';
const ONE_HOUR_SECONDS: number = 3600; const ONE_HOUR_SECONDS: number = 3600;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS; const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@ -85,7 +85,7 @@ export class VulnerabilityConfigComponent implements OnInit {
return []; return [];
} }
//UTC time // UTC time
get dailyTime(): string { get dailyTime(): string {
if (!(this.config && if (!(this.config &&
this.config.scan_all_policy && this.config.scan_all_policy &&
@ -94,7 +94,7 @@ export class VulnerabilityConfigComponent implements OnInit {
return "00:00"; return "00:00";
} }
let timeOffset: number = 0;//seconds let timeOffset: number = 0; // seconds
if (this.config.scan_all_policy.value.parameter) { if (this.config.scan_all_policy.value.parameter) {
let daily_time = this.config.scan_all_policy.value.parameter.daily_time; let daily_time = this.config.scan_all_policy.value.parameter.daily_time;
if (daily_time && typeof daily_time === "number") { if (daily_time && typeof daily_time === "number") {
@ -102,9 +102,9 @@ export class VulnerabilityConfigComponent implements OnInit {
} }
} }
//Convert to current time // Convert to current time
let timezoneOffset: number = this._localTime.getTimezoneOffset(); let timezoneOffset: number = this._localTime.getTimezoneOffset();
//Local time // Local time
timeOffset = timeOffset - timezoneOffset * 60; timeOffset = timeOffset - timezoneOffset * 60;
if (timeOffset < 0) { if (timeOffset < 0) {
timeOffset = timeOffset + ONE_DAY_SECONDS; timeOffset = timeOffset + ONE_DAY_SECONDS;
@ -114,7 +114,7 @@ export class VulnerabilityConfigComponent implements OnInit {
timeOffset -= ONE_DAY_SECONDS; timeOffset -= ONE_DAY_SECONDS;
} }
//To time string // To time string
let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS); let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS);
let minutes: number = Math.floor((timeOffset - hours * ONE_HOUR_SECONDS) / 60); let minutes: number = Math.floor((timeOffset - hours * ONE_HOUR_SECONDS) / 60);
@ -143,7 +143,7 @@ export class VulnerabilityConfigComponent implements OnInit {
return; return;
} }
//Double confirm inner parameter existing. // Double confirm inner parameter existing.
if (!this.config.scan_all_policy.value.parameter) { if (!this.config.scan_all_policy.value.parameter) {
this.config.scan_all_policy.value.parameter = { this.config.scan_all_policy.value.parameter = {
daily_time: 0 daily_time: 0
@ -157,7 +157,7 @@ export class VulnerabilityConfigComponent implements OnInit {
let hours: number = +values[0]; let hours: number = +values[0];
let minutes: number = +values[1]; let minutes: number = +values[1];
//Convert to UTC time // Convert to UTC time
let timezoneOffset: number = this._localTime.getTimezoneOffset(); let timezoneOffset: number = this._localTime.getTimezoneOffset();
let utcTimes: number = hours * ONE_HOUR_SECONDS + minutes * 60; let utcTimes: number = hours * ONE_HOUR_SECONDS + minutes * 60;
utcTimes += timezoneOffset * 60; utcTimes += timezoneOffset * 60;
@ -172,14 +172,14 @@ export class VulnerabilityConfigComponent implements OnInit {
this.config.scan_all_policy.value.parameter.daily_time = utcTimes; this.config.scan_all_policy.value.parameter.daily_time = utcTimes;
} }
//Scanning type // Scanning type
get scanningType(): string { get scanningType(): string {
if (this.config && if (this.config &&
this.config.scan_all_policy && this.config.scan_all_policy &&
this.config.scan_all_policy.value) { this.config.scan_all_policy.value) {
return this.config.scan_all_policy.value.type; return this.config.scan_all_policy.value.type;
} else { } else {
//default // default
return "none"; return "none";
} }
} }
@ -191,12 +191,12 @@ export class VulnerabilityConfigComponent implements OnInit {
let type: string = (v && v.trim() !== "") ? v : "none"; let type: string = (v && v.trim() !== "") ? v : "none";
this.config.scan_all_policy.value.type = type; this.config.scan_all_policy.value.type = type;
if (type !== "daily") { if (type !== "daily") {
//No parameter // No parameter
if (this.config.scan_all_policy.value.parameter) { if (this.config.scan_all_policy.value.parameter) {
delete (this.config.scan_all_policy.value.parameter); delete (this.config.scan_all_policy.value.parameter);
} }
} else { } else {
//Has parameter // Has parameter
if (!this.config.scan_all_policy.value.parameter) { if (!this.config.scan_all_policy.value.parameter) {
this.config.scan_all_policy.value.parameter = { this.config.scan_all_policy.value.parameter = {
daily_time: 0 daily_time: 0
@ -251,11 +251,11 @@ export class VulnerabilityConfigComponent implements OnInit {
scanNow(): void { scanNow(): void {
if (this.onSubmitting) { if (this.onSubmitting) {
return;//Aoid duplicated submitting return; // Aoid duplicated submitting
} }
if(!this.scanAvailable) { if (!this.scanAvailable) {
return; //Aoid page hacking return; // Aoid page hacking
} }
this.onSubmitting = true; this.onSubmitting = true;
@ -265,7 +265,7 @@ export class VulnerabilityConfigComponent implements OnInit {
this.errorHandler.info(res); this.errorHandler.info(res);
}); });
//Update system info // Update system info
this.getSystemInfo().then(() => { this.getSystemInfo().then(() => {
this.onSubmitting = false; this.onSubmitting = false;
}).catch(() => { }).catch(() => {
@ -284,9 +284,9 @@ export class VulnerabilityConfigComponent implements OnInit {
}); });
} }
getSystemInfo(): Promise<SystemInfo> { getSystemInfo(): Promise<void | SystemInfo> {
return toPromise<SystemInfo>(this.systemInfoService.getSystemInfo()) return toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then((info: SystemInfo) => this.systemInfo = info) .then((info: SystemInfo) => this.systemInfo = info)
.catch(error => this.errorHandler.error(error)); .catch(error => this.errorHandler.error(error));
} }
} }

View File

@ -11,21 +11,31 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const'; import {
ConfirmationTargets,
ConfirmationButtons
} from "../shared/shared.const";
export class ConfirmationMessage { export class ConfirmationMessage {
public constructor(title: string, message: string, param: string, data: any, targetId: ConfirmationTargets, buttons?: ConfirmationButtons) { public constructor(
this.title = title; title: string,
this.message = message; message: string,
this.data = data; param: string,
this.targetId = targetId; data: any,
this.param = param; targetId: ConfirmationTargets,
this.buttons = buttons ? buttons : ConfirmationButtons.CONFIRM_CANCEL; buttons?: ConfirmationButtons
} ) {
title: string; this.title = title;
message: string; this.message = message;
data: any = {};//default is empty this.data = data;
targetId: ConfirmationTargets = ConfirmationTargets.EMPTY; this.targetId = targetId;
param: string; this.param = param;
buttons: ConfirmationButtons; this.buttons = buttons ? buttons : ConfirmationButtons.CONFIRM_CANCEL;
} }
title: string;
message: string;
data: any = {}; // default is empty
targetId: ConfirmationTargets = ConfirmationTargets.EMPTY;
param: string;
buttons: ConfirmationButtons;
}

View File

@ -23,4 +23,4 @@ export class ConfirmationAcknowledgement {
state: ConfirmationState = ConfirmationState.NA; state: ConfirmationState = ConfirmationState.NA;
data: any = {}; data: any = {};
source: ConfirmationTargets = ConfirmationTargets.EMPTY; source: ConfirmationTargets = ConfirmationTargets.EMPTY;
} }

View File

@ -1,6 +1,6 @@
import { Type } from '@angular/core'; import { Type } from "@angular/core";
import { ConfirmationDialogComponent } from './confirmation-dialog.component'; import { ConfirmationDialogComponent } from "./confirmation-dialog.component";
export * from "./confirmation-dialog.component"; export * from "./confirmation-dialog.component";
export * from "./confirmation-batch-message"; export * from "./confirmation-batch-message";
@ -9,4 +9,4 @@ export * from "./confirmation-state-message";
export const CONFIRMATION_DIALOG_DIRECTIVES: Type<any>[] = [ export const CONFIRMATION_DIALOG_DIRECTIVES: Type<any>[] = [
ConfirmationDialogComponent ConfirmationDialogComponent
]; ];

View File

@ -1,35 +1,39 @@
import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing'; import {
ComponentFixture,
TestBed,
async
} from "@angular/core/testing";
import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from "../shared/shared.module";
import { FilterComponent } from '../filter/filter.component'; import { FilterComponent } from "../filter/filter.component";
import { CreateEditEndpointComponent } from '../create-edit-endpoint/create-edit-endpoint.component';
import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
import { ErrorHandler } from '../error-handler/error-handler';
import { Endpoint } from '../service/interface';
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import { IServiceConfig, SERVICE_CONFIG } from '../service.config';
describe('CreateEditEndpointComponent (inline template)', () => {
import { CreateEditEndpointComponent } from "../create-edit-endpoint/create-edit-endpoint.component";
import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
import { ErrorHandler } from "../error-handler/error-handler";
import { Endpoint } from "../service/interface";
import {
EndpointService,
EndpointDefaultService
} from "../service/endpoint.service";
import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
describe("CreateEditEndpointComponent (inline template)", () => {
let mockData: Endpoint = { let mockData: Endpoint = {
"id": 1, id: 1,
"endpoint": "https://10.117.4.151", endpoint: "https://10.117.4.151",
"name": "target_01", name: "target_01",
"username": "admin", username: "admin",
"password": "", password: "",
"insecure": false, insecure: false,
"type": 0 type: 0
}; };
let comp: CreateEditEndpointComponent; let comp: CreateEditEndpointComponent;
let fixture: ComponentFixture<CreateEditEndpointComponent>; let fixture: ComponentFixture<CreateEditEndpointComponent>;
let config: IServiceConfig = { let config: IServiceConfig = {
systemInfoEndpoint: '/api/endpoints/testing' systemInfoEndpoint: "/api/endpoints/testing"
}; };
let endpointService: EndpointService; let endpointService: EndpointService;
@ -38,14 +42,12 @@ describe('CreateEditEndpointComponent (inline template)', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [SharedModule, NoopAnimationsModule],
SharedModule, declarations: [
NoopAnimationsModule FilterComponent,
CreateEditEndpointComponent,
InlineAlertComponent
], ],
declarations: [
FilterComponent,
CreateEditEndpointComponent,
InlineAlertComponent ],
providers: [ providers: [
ErrorHandler, ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config }, { provide: SERVICE_CONFIG, useValue: config },
@ -54,24 +56,26 @@ describe('CreateEditEndpointComponent (inline template)', () => {
}); });
})); }));
beforeEach(()=>{ beforeEach(() => {
fixture = TestBed.createComponent(CreateEditEndpointComponent); fixture = TestBed.createComponent(CreateEditEndpointComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
endpointService = fixture.debugElement.injector.get(EndpointService); endpointService = fixture.debugElement.injector.get(EndpointService);
spy = spyOn(endpointService, 'getEndpoint').and.returnValue(Promise.resolve(mockData)); spy = spyOn(endpointService, "getEndpoint").and.returnValue(
Promise.resolve(mockData)
);
fixture.detectChanges(); fixture.detectChanges();
comp.openCreateEditTarget(true, 1); comp.openCreateEditTarget(true, 1);
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should be created', () => { it("should be created", () => {
fixture.detectChanges(); fixture.detectChanges();
expect(comp).toBeTruthy(); expect(comp).toBeTruthy();
}); });
it('should get endpoint be called', async(() => { it("should get endpoint be called", async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -79,16 +83,16 @@ describe('CreateEditEndpointComponent (inline template)', () => {
}); });
})); }));
it('should get endpoint and open modal', async(() => { it("should get endpoint and open modal", async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(comp.target.name).toEqual('target_01'); expect(comp.target.name).toEqual("target_01");
}); });
})); }));
it('should endpoint be initialized', () => { it("should endpoint be initialized", () => {
fixture.detectChanges(); fixture.detectChanges();
expect(config.systemInfoEndpoint).toEqual('/api/endpoints/testing'); expect(config.systemInfoEndpoint).toEqual("/api/endpoints/testing");
}); });
}); });

View File

@ -12,371 +12,367 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { import {
Component, Component,
Output, Output,
EventEmitter, EventEmitter,
ViewChild, ViewChild,
AfterViewChecked, AfterViewChecked,
ChangeDetectorRef, ChangeDetectorRef,
OnDestroy OnDestroy
} from '@angular/core'; } from "@angular/core";
import { NgForm } from '@angular/forms'; import { NgForm } from "@angular/forms";
import { Subscription } from "rxjs/Subscription";
import { TranslateService } from "@ngx-translate/core";
import { EndpointService } from '../service/endpoint.service'; import { EndpointService } from "../service/endpoint.service";
import { ErrorHandler } from '../error-handler/index'; import { ErrorHandler } from "../error-handler/index";
import { ActionType } from '../shared/shared.const'; import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
import { Endpoint } from "../service/interface";
import { toPromise, clone, compareValue, isEmptyObject } from "../utils";
import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
import { Endpoint } from '../service/interface'; const FAKE_PASSWORD = "rjGcfuRu";
import { TranslateService } from '@ngx-translate/core';
import { toPromise, clone, compareValue, isEmptyObject } from '../utils';
import { Subscription } from 'rxjs/Subscription';
const FAKE_PASSWORD = 'rjGcfuRu';
@Component({ @Component({
selector: 'hbr-create-edit-endpoint', selector: "hbr-create-edit-endpoint",
templateUrl: './create-edit-endpoint.component.html', templateUrl: "./create-edit-endpoint.component.html",
styleUrls: ['./create-edit-endpoint.component.scss'] styleUrls: ["./create-edit-endpoint.component.scss"]
}) })
export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy { export class CreateEditEndpointComponent
modalTitle: string; implements AfterViewChecked, OnDestroy {
createEditDestinationOpened: boolean; modalTitle: string;
staticBackdrop: boolean = true; createEditDestinationOpened: boolean;
closable: boolean = false; staticBackdrop: boolean = true;
editable: boolean; closable: boolean = false;
editable: boolean;
target: Endpoint = this.initEndpoint(); target: Endpoint = this.initEndpoint();
initVal: Endpoint; initVal: Endpoint;
targetForm: NgForm; targetForm: NgForm;
@ViewChild('targetForm') @ViewChild("targetForm") currentForm: NgForm;
currentForm: NgForm;
testOngoing: boolean; testOngoing: boolean;
onGoing: boolean; onGoing: boolean;
endpointId: number | string; endpointId: number | string;
@ViewChild(InlineAlertComponent) @ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent;
inlineAlert: InlineAlertComponent;
@Output() reload = new EventEmitter<boolean>(); @Output() reload = new EventEmitter<boolean>();
timerHandler: any; timerHandler: any;
valueChangesSub: Subscription; valueChangesSub: Subscription;
formValues: { [key: string]: string } | any; formValues: { [key: string]: string } | any;
constructor( constructor(
private endpointService: EndpointService, private endpointService: EndpointService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private translateService: TranslateService, private translateService: TranslateService,
private ref: ChangeDetectorRef private ref: ChangeDetectorRef
) { } ) {}
public get isValid(): boolean { public get isValid(): boolean {
return !this.testOngoing && return (
!this.onGoing && !this.testOngoing &&
this.targetForm && !this.onGoing &&
this.targetForm.valid && this.targetForm &&
this.editable && this.targetForm.valid &&
!compareValue(this.target, this.initVal); this.editable &&
!compareValue(this.target, this.initVal)
);
}
public get inProgress(): boolean {
return this.onGoing || this.testOngoing;
}
setInsecureValue($event: any) {
this.target.insecure = !$event;
}
ngOnDestroy(): void {
if (this.valueChangesSub) {
this.valueChangesSub.unsubscribe();
} }
}
public get inProgress(): boolean { initEndpoint(): Endpoint {
return this.onGoing || this.testOngoing; return {
endpoint: "",
name: "",
username: "",
password: "",
insecure: false,
type: 0
};
}
open(): void {
this.createEditDestinationOpened = true;
}
close(): void {
this.createEditDestinationOpened = false;
}
reset(): void {
// Reset status variables
this.testOngoing = false;
this.onGoing = false;
// Reset data
this.target = this.initEndpoint();
this.initVal = this.initEndpoint();
this.formValues = null;
this.endpointId = "";
this.inlineAlert.close();
}
// Forcely refresh the view
forceRefreshView(duration: number): void {
// Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
} }
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => {
if (this.timerHandler) {
clearInterval(this.timerHandler);
this.timerHandler = null;
}
}, duration);
}
setInsecureValue($event: any) { openCreateEditTarget(editable: boolean, targetId?: number | string) {
this.target.insecure = !$event; this.editable = editable;
// reset
this.reset();
if (targetId) {
this.endpointId = targetId;
this.translateService
.get("DESTINATION.TITLE_EDIT")
.subscribe(res => (this.modalTitle = res));
toPromise<Endpoint>(this.endpointService.getEndpoint(targetId))
.then(target => {
this.target = target;
// Keep data cache
this.initVal = clone(target);
this.initVal.password = FAKE_PASSWORD;
this.target.password = FAKE_PASSWORD;
// Open the modal now
this.open();
this.forceRefreshView(2000);
})
.catch(error => this.errorHandler.error(error));
} else {
this.endpointId = "";
this.translateService
.get("DESTINATION.TITLE_ADD")
.subscribe(res => (this.modalTitle = res));
// Directly open the modal
this.open();
} }
}
ngOnDestroy(): void { testConnection() {
if (this.valueChangesSub) { let payload: Endpoint = this.initEndpoint();
this.valueChangesSub.unsubscribe(); if (!this.endpointId) {
} payload.endpoint = this.target.endpoint;
} payload.username = this.target.username;
payload.password = this.target.password;
initEndpoint(): Endpoint { payload.insecure = this.target.insecure;
return { } else {
endpoint: "", let changes: { [key: string]: any } = this.getChanges();
name: "", for (let prop of Object.keys(payload)) {
username: "", delete payload[prop];
password: "", }
insecure: false, payload.id = this.target.id;
type: 0 if (!isEmptyObject(changes)) {
}; let changekeys: { [key: string]: any } = Object.keys(this.getChanges());
}
open(): void {
this.createEditDestinationOpened = true;
}
close(): void {
this.createEditDestinationOpened = false;
}
reset(): void {
//Reset status variables
this.testOngoing = false;
this.onGoing = false;
//Reset data
this.target = this.initEndpoint();
this.initVal = this.initEndpoint();
this.formValues = null;
this.endpointId = '';
this.inlineAlert.close();
}
//Forcely refresh the view
forceRefreshView(duration: number): void {
//Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
}
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => {
if (this.timerHandler) {
clearInterval(this.timerHandler);
this.timerHandler = null;
}
}, duration);
}
openCreateEditTarget(editable: boolean, targetId?: number | string) {
this.editable = editable;
//reset
this.reset();
if (targetId) {
this.endpointId = targetId;
this.translateService.get('DESTINATION.TITLE_EDIT').subscribe(res => this.modalTitle = res);
toPromise<Endpoint>(this.endpointService
.getEndpoint(targetId))
.then(
target => {
this.target = target;
//Keep data cache
this.initVal = clone(target);
this.initVal.password = FAKE_PASSWORD;
this.target.password = FAKE_PASSWORD;
//Open the modal now
this.open();
this.forceRefreshView(2000);
})
.catch(error => this.errorHandler.error(error));
} else {
this.endpointId = '';
this.translateService.get('DESTINATION.TITLE_ADD').subscribe(res => this.modalTitle = res);
//Directly open the modal
this.open();
}
}
testConnection() {
let payload: Endpoint = this.initEndpoint();
if (!this.endpointId) {
payload.endpoint = this.target.endpoint;
payload.username = this.target.username;
payload.password = this.target.password;
payload.insecure = this.target.insecure;
}else {
let changes: {[key: string]: any} = this.getChanges();
for (let prop in payload) {
delete payload[prop];
}
payload.id = this.target.id;
if (!isEmptyObject(changes)) {
let changekeys: {[key: string]: any} = Object.keys(this.getChanges());
changekeys.forEach((key: string) => {
payload[key] = changes[key];
});
}
}
this.testOngoing = true;
toPromise<Endpoint>(this.endpointService
.pingEndpoint(payload))
.then(
response => {
this.inlineAlert.showInlineSuccess({ message: "DESTINATION.TEST_CONNECTION_SUCCESS" });
this.forceRefreshView(2000);
this.testOngoing = false;
}).catch(
error => {
this.inlineAlert.showInlineError('DESTINATION.TEST_CONNECTION_FAILURE');
this.forceRefreshView(2000);
this.testOngoing = false;
});
}
onSubmit() {
if (this.endpointId) {
this.updateEndpoint();
} else {
this.addEndpoint();
}
}
addEndpoint() {
if (this.onGoing) {
return;//Avoid duplicated submitting
}
this.onGoing = true;
toPromise<number>(this.endpointService
.createEndpoint(this.target))
.then(response => {
this.translateService.get('DESTINATION.CREATED_SUCCESS')
.subscribe(res => this.errorHandler.info(res));
this.reload.emit(true);
this.onGoing = false;
this.close();
this.forceRefreshView(2000);
}).catch(error => {
this.onGoing = false;
let errorMessageKey = this.handleErrorMessageKey(error.status);
this.translateService
.get(errorMessageKey)
.subscribe(res => {
this.inlineAlert.showInlineError(res);
});
this.forceRefreshView(2000);
}
);
}
updateEndpoint() {
if (this.onGoing) {
return;//Avoid duplicated submitting
}
let payload: Endpoint = this.initEndpoint();
for (let prop in payload) {
delete payload[prop];
}
let changes: {[key: string]: any} = this.getChanges();
if (isEmptyObject(changes)) {
return;
}
let changekeys: {[key: string]: any} = Object.keys(changes);
changekeys.forEach((key: string) => { changekeys.forEach((key: string) => {
payload[key] = changes[key]; payload[key] = changes[key];
}); });
}
if (!this.target.id) { return; }
this.onGoing = true;
toPromise<number>(this.endpointService
.updateEndpoint(this.target.id, payload))
.then(
response => {
this.translateService.get('DESTINATION.UPDATED_SUCCESS')
.subscribe(res => this.errorHandler.info(res));
this.reload.emit(true);
this.close();
this.onGoing = false;
this.forceRefreshView(2000);
})
.catch(
error => {
let errorMessageKey = this.handleErrorMessageKey(error.status);
this.translateService
.get(errorMessageKey)
.subscribe(res => {
this.inlineAlert.showInlineError(res);
});
this.onGoing = false;
this.forceRefreshView(2000);
}
);
} }
handleErrorMessageKey(status: number): string { this.testOngoing = true;
switch (status) { toPromise<Endpoint>(this.endpointService.pingEndpoint(payload))
case 409: .then(response => {
return 'DESTINATION.CONFLICT_NAME'; this.inlineAlert.showInlineSuccess({
case 400: message: "DESTINATION.TEST_CONNECTION_SUCCESS"
return 'DESTINATION.INVALID_NAME'; });
default: this.forceRefreshView(2000);
return 'UNKNOWN_ERROR'; this.testOngoing = false;
} })
.catch(error => {
this.inlineAlert.showInlineError("DESTINATION.TEST_CONNECTION_FAILURE");
this.forceRefreshView(2000);
this.testOngoing = false;
});
}
onSubmit() {
if (this.endpointId) {
this.updateEndpoint();
} else {
this.addEndpoint();
}
}
addEndpoint() {
if (this.onGoing) {
return; // Avoid duplicated submitting
} }
onCancel() { this.onGoing = true;
let changes: {[key: string]: any} = this.getChanges(); toPromise<number>(this.endpointService.createEndpoint(this.target))
if (!isEmptyObject(changes)) { .then(response => {
this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' }); this.translateService
}else { .get("DESTINATION.CREATED_SUCCESS")
this.close(); .subscribe(res => this.errorHandler.info(res));
if (this.targetForm) { this.reload.emit(true);
this.targetForm.reset(); this.onGoing = false;
}
}
}
confirmCancel(confirmed: boolean) {
this.inlineAlert.close();
this.close(); this.close();
this.forceRefreshView(2000);
})
.catch(error => {
this.onGoing = false;
let errorMessageKey = this.handleErrorMessageKey(error.status);
this.translateService.get(errorMessageKey).subscribe(res => {
this.inlineAlert.showInlineError(res);
});
this.forceRefreshView(2000);
});
}
updateEndpoint() {
if (this.onGoing) {
return; // Avoid duplicated submitting
} }
ngAfterViewChecked(): void { let payload: Endpoint = this.initEndpoint();
if (this.targetForm != this.currentForm) { for (let prop of Object.keys(payload)) {
this.targetForm = this.currentForm; delete payload[prop];
if (this.targetForm) { }
this.valueChangesSub = this.targetForm.valueChanges.subscribe((data: { [key: string]: string } | any) => { let changes: { [key: string]: any } = this.getChanges();
if (data) { if (isEmptyObject(changes)) {
//To avoid invalid change publish events return;
let keyNumber: number = 0; }
for (let key in data) { let changekeys: { [key: string]: any } = Object.keys(changes);
//Empty string "" is accepted
if (data[key] !== null) {
keyNumber++;
}
}
if (keyNumber !== 5) {
return;
}
if (!compareValue(this.formValues, data)) { changekeys.forEach((key: string) => {
this.formValues = data; payload[key] = changes[key];
this.inlineAlert.close(); });
}
} if (!this.target.id) {
}); return;
}
this.onGoing = true;
toPromise<number>(
this.endpointService.updateEndpoint(this.target.id, payload)
)
.then(response => {
this.translateService
.get("DESTINATION.UPDATED_SUCCESS")
.subscribe(res => this.errorHandler.info(res));
this.reload.emit(true);
this.close();
this.onGoing = false;
this.forceRefreshView(2000);
})
.catch(error => {
let errorMessageKey = this.handleErrorMessageKey(error.status);
this.translateService.get(errorMessageKey).subscribe(res => {
this.inlineAlert.showInlineError(res);
});
this.onGoing = false;
this.forceRefreshView(2000);
});
}
handleErrorMessageKey(status: number): string {
switch (status) {
case 409:
return "DESTINATION.CONFLICT_NAME";
case 400:
return "DESTINATION.INVALID_NAME";
default:
return "UNKNOWN_ERROR";
}
}
onCancel() {
let changes: { [key: string]: any } = this.getChanges();
if (!isEmptyObject(changes)) {
this.inlineAlert.showInlineConfirmation({
message: "ALERT.FORM_CHANGE_CONFIRMATION"
});
} else {
this.close();
if (this.targetForm) {
this.targetForm.reset();
}
}
}
confirmCancel(confirmed: boolean) {
this.inlineAlert.close();
this.close();
}
ngAfterViewChecked(): void {
if (this.targetForm !== this.currentForm) {
this.targetForm = this.currentForm;
if (this.targetForm) {
this.valueChangesSub = this.targetForm.valueChanges.subscribe(
(data: { [key: string]: string } | any) => {
if (data) {
// To avoid invalid change publish events
let keyNumber: number = 0;
for (let key in data) {
// Empty string "" is accepted
if (data[key] !== null) {
keyNumber++;
}
}
if (keyNumber !== 5) {
return;
}
if (!compareValue(this.formValues, data)) {
this.formValues = data;
this.inlineAlert.close();
}
} }
} }
);
}
} }
getChanges(): { [key: string]: any | any[] } { }
let changes: { [key: string]: any | any[] } = {}; getChanges(): { [key: string]: any | any[] } {
if (!this.target || !this.initVal) { let changes: { [key: string]: any | any[] } = {};
return changes; if (!this.target || !this.initVal) {
} return changes;
for (let prop in this.target) { }
let field: any = this.initVal[prop]; for (let prop of Object.keys(this.target)) {
if (!compareValue(field, this.target[prop])) { let field: any = this.initVal[prop];
changes[prop] = this.target[prop]; if (!compareValue(field, this.target[prop])) {
//Number changes[prop] = this.target[prop];
if (typeof field === "number") { // Number
changes[prop] = +changes[prop]; if (typeof field === "number") {
} changes[prop] = +changes[prop];
//Trim string value
if (typeof field === "string") {
changes[prop] = ('' + changes[prop]).trim();
}
}
} }
return changes; // Trim string value
if (typeof field === "string") {
changes[prop] = ("" + changes[prop]).trim();
}
}
} }
return changes;
}
} }

View File

@ -4,4 +4,4 @@ import { CreateEditEndpointComponent } from './create-edit-endpoint.component';
export const CREATE_EDIT_ENDPOINT_DIRECTIVES: Type<any>[] = [ export const CREATE_EDIT_ENDPOINT_DIRECTIVES: Type<any>[] = [
CreateEditEndpointComponent CreateEditEndpointComponent
]; ];

View File

@ -1,85 +1,84 @@
import { ComponentFixture, TestBed, async } from "@angular/core/testing";
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { SharedModule } from '../shared/shared.module';
import { FilterComponent } from '../filter/filter.component'; import { SharedModule } from "../shared/shared.module";
import { FilterComponent } from "../filter/filter.component";
import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
import { ErrorHandler } from "../error-handler/error-handler";
import { Label } from "../service/interface";
import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
import { CreateEditLabelComponent } from "./create-edit-label.component";
import { LabelDefaultService, LabelService } from "../service/label.service";
import { InlineAlertComponent } from '../inline-alert/inline-alert.component'; describe("CreateEditLabelComponent (inline template)", () => {
import { ErrorHandler } from '../error-handler/error-handler'; let mockOneData: Label = {
import {Label} from '../service/interface'; color: "#9b0d54",
import { IServiceConfig, SERVICE_CONFIG } from '../service.config'; creation_time: "",
import {CreateEditLabelComponent} from "./create-edit-label.component"; description: "",
import {LabelDefaultService, LabelService} from "../service/label.service"; id: 1,
name: "label0-g",
project_id: 0,
scope: "g",
update_time: ""
};
describe('CreateEditLabelComponent (inline template)', () => { let comp: CreateEditLabelComponent;
let fixture: ComponentFixture<CreateEditLabelComponent>;
let mockOneData: Label = { let config: IServiceConfig = {
color: "#9b0d54", systemInfoEndpoint: "/api/label/testing"
creation_time: "", };
description: "",
id: 1,
name: "label0-g",
project_id: 0,
scope: "g",
update_time: "",
}
let comp: CreateEditLabelComponent; let labelService: LabelService;
let fixture: ComponentFixture<CreateEditLabelComponent>;
let config: IServiceConfig = { let spy: jasmine.Spy;
systemInfoEndpoint: '/api/label/testing' let spyOne: jasmine.Spy;
};
let labelService: LabelService; beforeEach(async(() => {
TestBed.configureTestingModule({
let spy: jasmine.Spy; imports: [SharedModule, NoopAnimationsModule],
let spyOne: jasmine.Spy; declarations: [
FilterComponent,
beforeEach(async(() => { CreateEditLabelComponent,
TestBed.configureTestingModule({ InlineAlertComponent
imports: [ ],
SharedModule, providers: [
NoopAnimationsModule ErrorHandler,
], { provide: SERVICE_CONFIG, useValue: config },
declarations: [ { provide: LabelService, useClass: LabelDefaultService }
FilterComponent, ]
CreateEditLabelComponent,
InlineAlertComponent ],
providers: [
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: LabelService, useClass: LabelDefaultService }
]
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(CreateEditLabelComponent);
comp = fixture.componentInstance;
labelService = fixture.debugElement.injector.get(LabelService);
spy = spyOn(labelService, 'getLabels').and.returnValue(Promise.resolve(mockOneData));
spyOne = spyOn(labelService, 'createLabel').and.returnValue(Promise.resolve(mockOneData));
fixture.detectChanges();
comp.openModal();
fixture.detectChanges();
}); });
}));
it('should be created', () => { beforeEach(() => {
fixture.detectChanges(); fixture = TestBed.createComponent(CreateEditLabelComponent);
expect(comp).toBeTruthy(); comp = fixture.componentInstance;
labelService = fixture.debugElement.injector.get(LabelService);
spy = spyOn(labelService, "getLabels").and.returnValue(
Promise.resolve(mockOneData)
);
spyOne = spyOn(labelService, "createLabel").and.returnValue(
Promise.resolve(mockOneData)
);
fixture.detectChanges();
comp.openModal();
fixture.detectChanges();
});
it("should be created", () => {
fixture.detectChanges();
expect(comp).toBeTruthy();
});
it("should get label and open modal", async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(comp.labelModel.name).toEqual("");
}); });
}));
it('should get label and open modal', async(() => { });
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(comp.labelModel.name).toEqual('');
});
}));
});

View File

@ -12,163 +12,177 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { import {
Component, Component,
Output, Output,
EventEmitter, EventEmitter,
OnDestroy, OnDestroy,
Input, OnInit, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef Input,
} from '@angular/core'; OnInit,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef
} from "@angular/core";
import {Label} from '../service/interface'; import { Label } from "../service/interface";
import {toPromise, clone, compareValue} from '../utils'; import { toPromise, clone, compareValue } from "../utils";
import {LabelService} from "../service/label.service"; import { LabelService } from "../service/label.service";
import {ErrorHandler} from "../error-handler/error-handler"; import { ErrorHandler } from "../error-handler/error-handler";
import {NgForm} from "@angular/forms"; import { NgForm } from "@angular/forms";
import {Subject} from "rxjs/Subject"; import { Subject } from "rxjs/Subject";
import {LabelColor} from "../shared/shared.const"; import { LabelColor } from "../shared/shared.const";
@Component({ @Component({
selector: 'hbr-create-edit-label', selector: "hbr-create-edit-label",
templateUrl: './create-edit-label.component.html', templateUrl: "./create-edit-label.component.html",
styleUrls: ['./create-edit-label.component.scss'], styleUrls: ["./create-edit-label.component.scss"],
changeDetection: ChangeDetectionStrategy.Default changeDetection: ChangeDetectionStrategy.Default
}) })
export class CreateEditLabelComponent implements OnInit, OnDestroy { export class CreateEditLabelComponent implements OnInit, OnDestroy {
formShow: boolean; formShow: boolean;
inProgress: boolean; inProgress: boolean;
copeLabelModel: Label; copeLabelModel: Label;
labelModel: Label = this.initLabel(); labelModel: Label = this.initLabel();
labelId = 0; labelId = 0;
checkOnGoing: boolean; checkOnGoing: boolean;
isLabelNameExist = false; isLabelNameExist = false;
panelHidden = true; panelHidden = true;
nameChecker = new Subject<string>(); nameChecker = new Subject<string>();
labelForm: NgForm; labelForm: NgForm;
@ViewChild('labelForm') @ViewChild("labelForm") currentForm: NgForm;
currentForm: NgForm;
@Input() projectId: number; @Input() projectId: number;
@Input() scope: string; @Input() scope: string;
@Output() reload = new EventEmitter(); @Output() reload = new EventEmitter();
constructor( constructor(
private labelService: LabelService, private labelService: LabelService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private ref: ChangeDetectorRef private ref: ChangeDetectorRef
) { } ) {}
ngOnInit(): void { ngOnInit(): void {
this.nameChecker.debounceTime(500).subscribe((name: string) => { this.nameChecker.debounceTime(500).subscribe((name: string) => {
this.checkOnGoing = true; this.checkOnGoing = true;
let labelName = this.currentForm.controls['name'].value; let labelName = this.currentForm.controls["name"].value;
toPromise<Label[]>(this.labelService.getLabels(this.scope, this.projectId, labelName)) toPromise<Label[]>(
.then(targets => { this.labelService.getLabels(this.scope, this.projectId, labelName)
if (targets && targets.length) { )
this.isLabelNameExist = true; .then(targets => {
}else { if (targets && targets.length) {
this.isLabelNameExist = false; this.isLabelNameExist = true;
} } else {
this.checkOnGoing = false; this.isLabelNameExist = false;
}).catch(error => { }
this.checkOnGoing = false; this.checkOnGoing = false;
this.errorHandler.error(error) })
}); .catch(error => {
setTimeout(() => { this.checkOnGoing = false;
setInterval(() => this.ref.markForCheck(), 100); this.errorHandler.error(error);
}, 3000); });
setTimeout(() => {
setInterval(() => this.ref.markForCheck(), 100);
}, 3000);
});
}
ngOnDestroy(): void {
this.nameChecker.unsubscribe();
}
get labelColor() {
return LabelColor;
}
initLabel(): Label {
return {
name: "",
description: "",
color: "",
scope: "",
project_id: 0
};
}
openModal(): void {
this.labelModel = this.initLabel();
this.formShow = true;
this.isLabelNameExist = false;
this.labelId = 0;
this.copeLabelModel = null;
}
editModel(labelId: number, label: Label[]): void {
this.labelModel = clone(label[0]);
this.formShow = true;
this.labelId = labelId;
this.copeLabelModel = clone(label[0]);
}
openColorPanel(): void {
this.panelHidden = false;
}
closeColorPanel(): void {
this.panelHidden = true;
}
public get hasChanged(): boolean {
return !compareValue(this.copeLabelModel, this.labelModel);
}
public get isValid(): boolean {
return !(
this.checkOnGoing ||
this.isLabelNameExist ||
!(this.currentForm && this.currentForm.valid) ||
!this.hasChanged ||
this.inProgress
);
}
existValid(text: string): void {
if (text) {
this.nameChecker.next(text);
}
}
onSubmit(): void {
this.inProgress = true;
if (this.labelId <= 0) {
this.labelModel.scope = this.scope;
this.labelModel.project_id = this.projectId;
toPromise<Label>(this.labelService.createLabel(this.labelModel))
.then(res => {
this.inProgress = false;
this.reload.emit();
this.labelModel = this.initLabel();
})
.catch(err => {
this.inProgress = false;
this.errorHandler.error(err);
});
} else {
toPromise<Label>(
this.labelService.updateLabel(this.labelId, this.labelModel)
)
.then(res => {
this.inProgress = false;
this.reload.emit();
this.labelModel = this.initLabel();
})
.catch(err => {
this.inProgress = false;
this.errorHandler.error(err);
}); });
} }
}
ngOnDestroy(): void { onCancel(): void {
this.nameChecker.unsubscribe(); this.inProgress = false;
} this.labelModel = this.initLabel();
this.formShow = false;
get labelColor() { }
return LabelColor;
}
initLabel(): Label {
return {
name: '',
description: '',
color: '',
scope: '',
project_id: 0
};
}
openModal(): void {
this.labelModel = this.initLabel();
this.formShow = true;
this.isLabelNameExist = false;
this.labelId = 0;
this.copeLabelModel = null;
}
editModel(labelId: number, label: Label[]): void {
this.labelModel = clone(label[0]);
this.formShow = true;
this.labelId = labelId;
this.copeLabelModel = clone(label[0]);
}
openColorPanel(): void {
this.panelHidden = false;
}
closeColorPanel(): void {
this.panelHidden = true;
}
public get hasChanged(): boolean {
return !compareValue(this.copeLabelModel, this.labelModel);
}
public get isValid(): boolean {
return !(this.checkOnGoing || this.isLabelNameExist || !(this.currentForm && this.currentForm.valid) || !this.hasChanged || this.inProgress);
}
existValid(text: string): void {
if (text) {
this.nameChecker.next(text);
}
}
onSubmit(): void {
this.inProgress = true;
if (this.labelId <= 0) {
this.labelModel.scope = this.scope;
this.labelModel.project_id = this.projectId;
toPromise<Label>(this.labelService.createLabel(this.labelModel))
.then(res => {
this.inProgress = false;
this.reload.emit();
this.labelModel = this.initLabel();
}).catch(err => {
this.inProgress = false;
this.errorHandler.error(err)
});
} else {
toPromise<Label>(this.labelService.updateLabel(this.labelId, this.labelModel))
.then(res => {
this.inProgress = false;
this.reload.emit();
this.labelModel = this.initLabel();
}).catch(err => {
this.inProgress = false;
this.errorHandler.error(err)
});
}
}
onCancel(): void {
this.inProgress = false;
this.labelModel = this.initLabel();
this.formShow = false;
}
} }

View File

@ -1,6 +1,6 @@
import { Type } from '@angular/core'; import { Type } from "@angular/core";
import {CreateEditLabelComponent} from "./create-edit-label.component"; import { CreateEditLabelComponent } from "./create-edit-label.component";
export const CREATE_EDIT_LABEL_DIRECTIVES: Type<any>[] = [ export const CREATE_EDIT_LABEL_DIRECTIVES: Type<any>[] = [
CreateEditLabelComponent CreateEditLabelComponent
]; ];

View File

@ -1,214 +1,211 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { ComponentFixture, TestBed, async } from "@angular/core/testing";
import { By } from '@angular/platform-browser'; import { By } from "@angular/platform-browser";
import { DebugElement } from '@angular/core'; import { DebugElement } from "@angular/core";
import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from "../shared/shared.module";
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ReplicationComponent } from '../replication/replication.component'; import { ReplicationComponent } from "../replication/replication.component";
import { ListReplicationRuleComponent } from '../list-replication-rule/list-replication-rule.component'; import { ListReplicationRuleComponent } from "../list-replication-rule/list-replication-rule.component";
import { CreateEditRuleComponent } from './create-edit-rule.component'; import { CreateEditRuleComponent } from "./create-edit-rule.component";
import { DatePickerComponent } from '../datetime-picker/datetime-picker.component'; import { DatePickerComponent } from "../datetime-picker/datetime-picker.component";
import { DateValidatorDirective } from '../datetime-picker/date-validator.directive'; import { FilterComponent } from "../filter/filter.component";
import { FilterComponent } from '../filter/filter.component'; import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
import { InlineAlertComponent } from '../inline-alert/inline-alert.component'; import {
import { ReplicationRule, ReplicationJob, Endpoint, ReplicationJobItem } from '../service/interface'; ReplicationRule,
ReplicationJob,
Endpoint,
ReplicationJobItem
} from "../service/interface";
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from "../error-handler/error-handler";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
import { import {
ReplicationService, ReplicationService,
ReplicationDefaultService, ReplicationDefaultService,
JobLogService, JobLogService,
JobLogDefaultService JobLogDefaultService
} from '../service/index'; } from "../service/index";
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service'; import {
import {ProjectDefaultService, ProjectService} from "../service/project.service"; EndpointService,
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component'; EndpointDefaultService
import {Project} from "../project-policy-config/project"; } from "../service/endpoint.service";
import {
describe('CreateEditRuleComponent (inline template)', ()=>{ ProjectDefaultService,
ProjectService
} from "../service/project.service";
import { JobLogViewerComponent } from "../job-log-viewer/job-log-viewer.component";
describe("CreateEditRuleComponent (inline template)", () => {
let mockRules: ReplicationRule[] = [ let mockRules: ReplicationRule[] = [
{ {
"id": 1, id: 1,
"name": "sync_01", name: "sync_01",
"description": "", description: "",
"projects": [{ "project_id": 1, projects: [
"owner_id": 0, {
"name": 'project_01', project_id: 1,
"creation_time": '', owner_id: 0,
"deleted": 0, name: "project_01",
"owner_name": '', creation_time: "",
"togglable": false, deleted: 0,
"update_time": '', owner_name: "",
"current_user_role_id": 0, togglable: false,
"repo_count": 0, update_time: "",
"has_project_admin_role": false, current_user_role_id: 0,
"is_member": false, repo_count: 0,
"role_name": '', has_project_admin_role: false,
"metadata": { is_member: false,
"public": '', role_name: "",
"enable_content_trust": '', metadata: {
"prevent_vul": '', public: "",
"severity": '', enable_content_trust: "",
"auto_scan": '', prevent_vul: "",
severity: "",
auto_scan: ""
} }
}], }
"targets": [{ ],
"id": 1, targets: [
"endpoint": "https://10.117.4.151", {
"name": "target_01", id: 1,
"username": "admin", endpoint: "https://10.117.4.151",
"password": "", name: "target_01",
"insecure": false, username: "admin",
"type": 0 password: "",
}], insecure: false,
"trigger": { type: 0
"kind": "Manual", }
"schedule_param": null ],
}, trigger: {
"filters": [], kind: "Manual",
"replicate_existing_image_now": false, schedule_param: null
"replicate_deletion": false, },
}] filters: [],
replicate_existing_image_now: false,
replicate_deletion: false
}
];
let mockJobs: ReplicationJobItem[] = [ let mockJobs: ReplicationJobItem[] = [
{ {
"id": 1, id: 1,
"status": "stopped", status: "stopped",
"repository": "library/busybox", repository: "library/busybox",
"policy_id": 1, policy_id: 1,
"operation": "transfer", operation: "transfer",
"tags": null tags: null
}, },
{ {
"id": 2, id: 2,
"status": "stopped", status: "stopped",
"repository": "library/busybox", repository: "library/busybox",
"policy_id": 1, policy_id: 1,
"operation": "transfer", operation: "transfer",
"tags": null tags: null
}, },
{ {
"id": 3, id: 3,
"status": "stopped", status: "stopped",
"repository": "library/busybox", repository: "library/busybox",
"policy_id": 2, policy_id: 2,
"operation": "transfer", operation: "transfer",
"tags": null tags: null
} }
]; ];
let mockJob: ReplicationJob = { let mockJob: ReplicationJob = {
metadata: {xTotalCount: 3}, metadata: { xTotalCount: 3 },
data: mockJobs data: mockJobs
}; };
let mockEndpoints: Endpoint[] = [ let mockEndpoints: Endpoint[] = [
{ {
"id": 1, id: 1,
"endpoint": "https://10.117.4.151", endpoint: "https://10.117.4.151",
"name": "target_01", name: "target_01",
"username": "admin", username: "admin",
"password": "", password: "",
"insecure": false, insecure: false,
"type": 0 type: 0
}, },
{ {
"id": 2, id: 2,
"endpoint": "https://10.117.5.142", endpoint: "https://10.117.5.142",
"name": "target_02", name: "target_02",
"username": "AAA", username: "AAA",
"password": "", password: "",
"insecure": false, insecure: false,
"type": 0 type: 0
}, },
{ {
"id": 3, id: 3,
"endpoint": "https://101.1.11.111", endpoint: "https://101.1.11.111",
"name": "target_03", name: "target_03",
"username": "admin", username: "admin",
"password": "", password: "",
"insecure": false, insecure: false,
"type": 0 type: 0
}, },
{ {
"id": 4, id: 4,
"endpoint": "http://4.4.4.4", endpoint: "http://4.4.4.4",
"name": "target_04", name: "target_04",
"username": "", username: "",
"password": "", password: "",
"insecure": true, insecure: true,
"type": 0 type: 0
} }
]; ];
let mockRule: ReplicationRule = { let mockRule: ReplicationRule = {
"id": 1, id: 1,
"name": "sync_01", name: "sync_01",
"description": "", description: "",
"projects": [{ "project_id": 1, projects: [
"owner_id": 0, {
"name": 'project_01', project_id: 1,
"creation_time": '', owner_id: 0,
"deleted": 0, name: "project_01",
"owner_name": '', creation_time: "",
"togglable": false, deleted: 0,
"update_time": '', owner_name: "",
"current_user_role_id": 0, togglable: false,
"repo_count": 0, update_time: "",
"has_project_admin_role": false, current_user_role_id: 0,
"is_member": false, repo_count: 0,
"role_name": '', has_project_admin_role: false,
"metadata": { is_member: false,
"public": '', role_name: "",
"enable_content_trust": '', metadata: {
"prevent_vul": '', public: "",
"severity": '', enable_content_trust: "",
"auto_scan": '', prevent_vul: "",
} severity: "",
}], auto_scan: ""
"targets": [{ }
"id": 1, }
"endpoint": "https://10.117.4.151", ],
"name": "target_01", targets: [
"username": "admin", {
"password": "", id: 1,
"insecure": false, endpoint: "https://10.117.4.151",
"type": 0 name: "target_01",
}], username: "admin",
"trigger": { password: "",
"kind": "Manual", insecure: false,
"schedule_param": null type: 0
}, }
"filters": [], ],
"replicate_existing_image_now": false, trigger: {
"replicate_deletion": false, kind: "Manual",
} schedule_param: null
let mockProjects: Project[] = [ },
{ "project_id": 1, filters: [],
"owner_id": 0, replicate_existing_image_now: false,
"name": 'project_01', replicate_deletion: false
"creation_time": '', };
"deleted": 0,
"owner_name": '',
"togglable": false,
"update_time": '',
"current_user_role_id": 0,
"repo_count": 0,
"has_project_admin_role": false,
"is_member": false,
"role_name": '',
"metadata": {
"public": '',
"enable_content_trust": '',
"prevent_vul": '',
"severity": '',
"auto_scan": '',
}
}];
let fixture: ComponentFixture<ReplicationComponent>; let fixture: ComponentFixture<ReplicationComponent>;
let fixtureCreate: ComponentFixture<CreateEditRuleComponent>; let fixtureCreate: ComponentFixture<CreateEditRuleComponent>;
@ -226,16 +223,13 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
let spyEndpoint: jasmine.Spy; let spyEndpoint: jasmine.Spy;
let config: IServiceConfig = { let config: IServiceConfig = {
replicationJobEndpoint: '/api/jobs/replication/testing', replicationJobEndpoint: "/api/jobs/replication/testing",
targetBaseEndpoint: '/api/targets/testing' targetBaseEndpoint: "/api/targets/testing"
}; };
beforeEach(async(() =>{ beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [SharedModule, NoopAnimationsModule],
SharedModule,
NoopAnimationsModule
],
declarations: [ declarations: [
ReplicationComponent, ReplicationComponent,
ListReplicationRuleComponent, ListReplicationRuleComponent,
@ -257,7 +251,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
}); });
})); }));
beforeEach(()=>{ beforeEach(() => {
fixture = TestBed.createComponent(ReplicationComponent); fixture = TestBed.createComponent(ReplicationComponent);
fixtureCreate = TestBed.createComponent(CreateEditRuleComponent); fixtureCreate = TestBed.createComponent(CreateEditRuleComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
@ -267,47 +261,53 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
replicationService = fixture.debugElement.injector.get(ReplicationService); replicationService = fixture.debugElement.injector.get(ReplicationService);
endpointService = fixtureCreate.debugElement.injector.get(EndpointService);
endpointService = fixtureCreate.debugElement.injector.get(EndpointService) ; spyRules = spyOn(
replicationService,
"getReplicationRules"
).and.returnValues(Promise.resolve(mockRules));
spyOneRule = spyOn(
replicationService,
"getReplicationRule"
).and.returnValue(Promise.resolve(mockRule));
spyJobs = spyOn(replicationService, "getJobs").and.returnValues(
Promise.resolve(mockJob)
);
spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(Promise.resolve(mockRules)); spyEndpoint = spyOn(endpointService, "getEndpoints").and.returnValues(
spyOneRule = spyOn(replicationService, 'getReplicationRule').and.returnValue(Promise.resolve(mockRule)); Promise.resolve(mockEndpoints)
spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJob)); );
spyEndpoint = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockEndpoints));
fixture.detectChanges(); fixture.detectChanges();
}); });
it('Should open creation modal and load endpoints', async(()=>{ it("Should open creation modal and load endpoints", async(() => {
fixture.detectChanges(); fixture.detectChanges();
compCreate.openCreateEditRule(); compCreate.openCreateEditRule();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(By.css('input')); let de: DebugElement = fixture.debugElement.query(By.css("input"));
expect(de).toBeTruthy(); expect(de).toBeTruthy();
let deSelect: DebugElement = fixture.debugElement.query(By.css('select')); let deSelect: DebugElement = fixture.debugElement.query(By.css("select"));
expect(deSelect).toBeTruthy(); expect(deSelect).toBeTruthy();
let elSelect: HTMLElement = de.nativeElement; let elSelect: HTMLElement = de.nativeElement;
expect(elSelect).toBeTruthy(); expect(elSelect).toBeTruthy();
expect(elSelect.childNodes.item(0).textContent).toEqual('target_01'); expect(elSelect.childNodes.item(0).textContent).toEqual("target_01");
}); });
})); }));
it('Should open modal to edit replication rule', async(()=>{ it("Should open modal to edit replication rule", async(() => {
fixture.detectChanges(); fixture.detectChanges();
compCreate.openCreateEditRule(mockRule.id); compCreate.openCreateEditRule(mockRule.id);
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(By.css('input')); let de: DebugElement = fixture.debugElement.query(By.css("input"));
expect(de).toBeTruthy(); expect(de).toBeTruthy();
fixture.detectChanges(); fixture.detectChanges();
let el: HTMLElement = de.nativeElement; let el: HTMLElement = de.nativeElement;
expect(el).toBeTruthy(); expect(el).toBeTruthy();
expect(el.textContent.trim()).toEqual('sync_01'); expect(el.textContent.trim()).toEqual("sync_01");
}); });
})); }));
}); });

View File

@ -11,32 +11,37 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, Input, EventEmitter, Output} from '@angular/core'; import {
import {Filter, ReplicationRule, Endpoint} from "../service/interface"; Component,
import {Subject} from "rxjs/Subject"; OnInit,
import {Subscription} from "rxjs/Subscription"; OnDestroy,
import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms"; ViewChild,
import {CreateEditEndpointComponent} from "../create-edit-endpoint/create-edit-endpoint.component"; ChangeDetectorRef,
import {Router, ActivatedRoute} from "@angular/router"; Input,
import {compareValue, isEmptyObject, toPromise} from "../utils"; EventEmitter,
import { InlineAlertComponent } from '../inline-alert/inline-alert.component'; Output
import {ReplicationService} from "../service/replication.service"; } from "@angular/core";
import {ErrorHandler} from "../error-handler/error-handler"; import { Filter, ReplicationRule, Endpoint } from "../service/interface";
import {TranslateService} from "@ngx-translate/core"; import { Subject } from "rxjs/Subject";
import {EndpointService} from "../service/endpoint.service"; import { Subscription } from "rxjs/Subscription";
import {ProjectService} from "../service/project.service"; import { FormArray, FormBuilder, FormGroup, Validators } from "@angular/forms";
import {Project} from "../project-policy-config/project"; import { compareValue, isEmptyObject, toPromise } from "../utils";
import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
import { ReplicationService } from "../service/replication.service";
import { ErrorHandler } from "../error-handler/error-handler";
import { TranslateService } from "@ngx-translate/core";
import { EndpointService } from "../service/endpoint.service";
import { ProjectService } from "../service/project.service";
import { Project } from "../project-policy-config/project";
const ONE_HOUR_SECONDS = 3600; const ONE_HOUR_SECONDS = 3600;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS; const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@Component ({ @Component({
selector: 'hbr-create-edit-rule', selector: "hbr-create-edit-rule",
templateUrl: './create-edit-rule.component.html', templateUrl: "./create-edit-rule.component.html",
styleUrls: ['./create-edit-rule.component.scss'] styleUrls: ["./create-edit-rule.component.scss"]
}) })
export class CreateEditRuleComponent implements OnInit, OnDestroy { export class CreateEditRuleComponent implements OnInit, OnDestroy {
_localTime: Date = new Date(); _localTime: Date = new Date();
targetList: Endpoint[] = []; targetList: Endpoint[] = [];
@ -51,15 +56,23 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
noSelectedProject = true; noSelectedProject = true;
noSelectedEndpoint = true; noSelectedEndpoint = true;
filterCount = 0; filterCount = 0;
triggerNames: string[] = ['Manual', 'Immediate', 'Scheduled']; triggerNames: string[] = ["Manual", "Immediate", "Scheduled"];
scheduleNames: string[] = ['Daily', 'Weekly']; scheduleNames: string[] = ["Daily", "Weekly"];
weekly: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; weekly: string[] = [
filterSelect: string[] = ['repository', 'tag']; "Monday",
ruleNameTooltip = 'TOOLTIP.EMPTY'; "Tuesday",
headerTitle = 'REPLICATION.ADD_POLICY'; "Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
];
filterSelect: string[] = ["repository", "tag"];
ruleNameTooltip = "TOOLTIP.EMPTY";
headerTitle = "REPLICATION.ADD_POLICY";
createEditRuleOpened: boolean; createEditRuleOpened: boolean;
filterListData: {[key: string]: any}[] = []; filterListData: { [key: string]: any }[] = [];
inProgress = false; inProgress = false;
inNameChecking = false; inNameChecking = false;
isRuleNameExist = false; isRuleNameExist = false;
@ -78,30 +91,30 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
@Output() goToRegistry = new EventEmitter<any>(); @Output() goToRegistry = new EventEmitter<any>();
@Output() reload = new EventEmitter<boolean>(); @Output() reload = new EventEmitter<boolean>();
@ViewChild(InlineAlertComponent) @ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent;
inlineAlert: InlineAlertComponent;
emptyProject = { emptyProject = {
project_id: -1, project_id: -1,
name: '', name: ""
} };
emptyEndpoint = { emptyEndpoint = {
id: -1, id: -1,
endpoint: '', endpoint: "",
name: '', name: "",
username: '', username: "",
password: '', password: "",
insecure: true, insecure: true,
type: 0, type: 0
} };
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private repService: ReplicationService, private repService: ReplicationService,
private endpointService: EndpointService, private endpointService: EndpointService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private proService: ProjectService, private proService: ProjectService,
private translateService: TranslateService, private translateService: TranslateService,
public ref: ChangeDetectorRef) { public ref: ChangeDetectorRef
) {
this.createForm(); this.createForm();
} }
@ -115,85 +128,99 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
toPromise<Endpoint[]>(this.endpointService toPromise<Endpoint[]>(this.endpointService.getEndpoints())
.getEndpoints()) .then(targets => {
.then(targets => { this.targetList = targets || [];
this.targetList = targets || []; })
}).catch((error: any) => this.errorHandler.error(error)); .catch((error: any) => this.errorHandler.error(error));
if (!this.projectId) { if (!this.projectId) {
toPromise<Project[]>(this.proService.listProjects("", undefined)) toPromise<Project[]>(this.proService.listProjects("", undefined))
.then(targets => { .then(targets => {
this.projectList = targets || []; this.projectList = targets || [];
}).catch(error => this.errorHandler.error(error)); })
.catch(error => this.errorHandler.error(error));
} }
this.nameChecker.debounceTime(500).distinctUntilChanged().subscribe((ruleName: string) => { this.nameChecker
this.isRuleNameExist = false; .debounceTime(500)
this.inNameChecking = true; .distinctUntilChanged()
toPromise<ReplicationRule[]>(this.repService.getReplicationRules(0, ruleName)) .subscribe((ruleName: string) => {
this.isRuleNameExist = false;
this.inNameChecking = true;
toPromise<ReplicationRule[]>(
this.repService.getReplicationRules(0, ruleName)
)
.then(response => { .then(response => {
if (response.some(rule => rule.name === ruleName)) { if (response.some(rule => rule.name === ruleName)) {
this.ruleNameTooltip = 'TOOLTIP.RULE_USER_EXISTING'; this.ruleNameTooltip = "TOOLTIP.RULE_USER_EXISTING";
this.isRuleNameExist = true; this.isRuleNameExist = true;
} }
this.inNameChecking = false; this.inNameChecking = false;
}).catch(() => { })
this.inNameChecking = false; .catch(() => {
this.inNameChecking = false;
});
}); });
});
this.proNameChecker this.proNameChecker
.debounceTime(500) .debounceTime(500)
.distinctUntilChanged() .distinctUntilChanged()
.subscribe((name: string) => { .subscribe((name: string) => {
this.noProjectInfo = ''; this.noProjectInfo = "";
this.selectedProjectList = []; this.selectedProjectList = [];
toPromise<Project[]>(this.proService.listProjects(name, undefined)).then((res: any) => { toPromise<Project[]>(this.proService.listProjects(name, undefined))
if (res) { .then((res: any) => {
this.selectedProjectList = res.slice(0, 10); if (res) {
// if input value exit in project list this.selectedProjectList = res.slice(0, 10);
let pro = res.find((data: any) => data.name === name); // if input value exit in project list
if (!pro) { let pro = res.find((data: any) => data.name === name);
this.noProjectInfo = 'REPLICATION.NO_PROJECT_INFO'; if (!pro) {
this.noSelectedProject = true; this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
} else {
this.noProjectInfo = '';
this.noSelectedProject = false;
this.setProject([pro])
}
} else {
this.noProjectInfo = 'REPLICATION.NO_PROJECT_INFO';
this.noSelectedProject = true; this.noSelectedProject = true;
} else {
this.noProjectInfo = "";
this.noSelectedProject = false;
this.setProject([pro]);
} }
}).catch((error: any) => { } else {
this.errorHandler.error(error); this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
this.noProjectInfo = 'REPLICATION.NO_PROJECT_INFO';
this.noSelectedProject = true; this.noSelectedProject = true;
}); }
}); })
.catch((error: any) => {
this.errorHandler.error(error);
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
this.noSelectedProject = true;
});
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.confirmSub) { if (this.confirmSub) {
this.confirmSub.unsubscribe(); this.confirmSub.unsubscribe();
}
if (this.nameChecker) {
this.nameChecker.unsubscribe();
}
if (this.proNameChecker) {
this.proNameChecker.unsubscribe();
}
} }
if (this.nameChecker) {
this.nameChecker.unsubscribe();
}
if (this.proNameChecker) {
this.proNameChecker.unsubscribe();
}
}
get isValid() { get isValid() {
return !(this.isRuleNameExist || this.noSelectedProject || this.noSelectedEndpoint || this.inProgress ); return !(
this.isRuleNameExist ||
this.noSelectedProject ||
this.noSelectedEndpoint ||
this.inProgress
);
} }
createForm() { createForm() {
this.ruleForm = this.fb.group({ this.ruleForm = this.fb.group({
name: ['', Validators.required], name: ["", Validators.required],
description: '', description: "",
projects: this.fb.array([]), projects: this.fb.array([]),
targets: this.fb.array([]), targets: this.fb.array([]),
trigger: this.fb.group({ trigger: this.fb.group({
@ -201,8 +228,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
schedule_param: this.fb.group({ schedule_param: this.fb.group({
type: this.scheduleNames[0], type: this.scheduleNames[0],
weekday: 1, weekday: 1,
offtime: '08:00' offtime: "08:00"
}), })
}), }),
filters: this.fb.array([]), filters: this.fb.array([]),
replicate_existing_image_now: true, replicate_existing_image_now: true,
@ -212,13 +239,16 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
initForm(): void { initForm(): void {
this.ruleForm.reset({ this.ruleForm.reset({
name: '', name: "",
description: '', description: "",
trigger: {kind: this.triggerNames[0], schedule_param: { trigger: {
kind: this.triggerNames[0],
schedule_param: {
type: this.scheduleNames[0], type: this.scheduleNames[0],
weekday: 1, weekday: 1,
offtime: '08:00' offtime: "08:00"
}}, }
},
replicate_existing_image_now: true, replicate_existing_image_now: true,
replicate_deletion: false replicate_deletion: false
}); });
@ -254,49 +284,49 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
} }
get projects(): FormArray { get projects(): FormArray {
return this.ruleForm.get('projects') as FormArray; return this.ruleForm.get("projects") as FormArray;
} }
setProject(projects: Project[]) { setProject(projects: Project[]) {
const projectFGs = projects.map(project => this.fb.group(project)); const projectFGs = projects.map(project => this.fb.group(project));
const projectFormArray = this.fb.array(projectFGs); const projectFormArray = this.fb.array(projectFGs);
this.ruleForm.setControl('projects', projectFormArray); this.ruleForm.setControl("projects", projectFormArray);
} }
get filters(): FormArray { get filters(): FormArray {
return this.ruleForm.get('filters') as FormArray; return this.ruleForm.get("filters") as FormArray;
} }
setFilter(filters: Filter[]) { setFilter(filters: Filter[]) {
const filterFGs = filters.map(filter => this.fb.group(filter)); const filterFGs = filters.map(filter => this.fb.group(filter));
const filterFormArray = this.fb.array(filterFGs); const filterFormArray = this.fb.array(filterFGs);
this.ruleForm.setControl('filters', filterFormArray); this.ruleForm.setControl("filters", filterFormArray);
} }
get targets(): FormArray { get targets(): FormArray {
return this.ruleForm.get('targets') as FormArray; return this.ruleForm.get("targets") as FormArray;
} }
setTarget(targets: Endpoint[]) { setTarget(targets: Endpoint[]) {
const targetFGs = targets.map(target => this.fb.group(target)); const targetFGs = targets.map(target => this.fb.group(target));
const targetFormArray = this.fb.array(targetFGs); const targetFormArray = this.fb.array(targetFGs);
this.ruleForm.setControl('targets', targetFormArray); this.ruleForm.setControl("targets", targetFormArray);
} }
initFilter(name: string) { initFilter(name: string) {
return this.fb.group({ return this.fb.group({
kind: name, kind: name,
pattern: ['', Validators.required] pattern: ["", Validators.required]
}); });
} }
filterChange($event: any) { filterChange($event: any) {
if ($event && $event.target['value']) { if ($event && $event.target["value"]) {
let id: number = $event.target.id; let id: number = $event.target.id;
let name: string = $event.target.name; let name: string = $event.target.name;
let value: string = $event.target['value']; let value: string = $event.target["value"];
this.filterListData.forEach((data, index) => { this.filterListData.forEach((data, index) => {
if (index === +id) { if (index === +id) {
data.name = $event.target.name = value; data.name = $event.target.name = value;
}else { } else {
data.options.splice(data.options.indexOf(value), 1); data.options.splice(data.options.indexOf(value), 1);
} }
if (data.options.indexOf(name) === -1) { if (data.options.indexOf(name) === -1) {
@ -308,30 +338,32 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
targetChange($event: any) { targetChange($event: any) {
if ($event && $event.target) { if ($event && $event.target) {
if ($event.target['value'] === '-1') { if ($event.target["value"] === "-1") {
this.noSelectedEndpoint = true; this.noSelectedEndpoint = true;
return; return;
} }
let selecedTarget: Endpoint = this.targetList.find(target => target.id === +$event.target['value']); let selecedTarget: Endpoint = this.targetList.find(
target => target.id === +$event.target["value"]
);
this.setTarget([selecedTarget]); this.setTarget([selecedTarget]);
this.noSelectedEndpoint = false; this.noSelectedEndpoint = false;
} }
} }
// Handle the form validation // Handle the form validation
handleValidation(): void { handleValidation(): void {
let cont = this.ruleForm.controls["projects"]; let cont = this.ruleForm.controls["projects"];
if (cont && cont.valid) { if (cont && cont.valid) {
this.proNameChecker.next(cont.value[0].name); this.proNameChecker.next(cont.value[0].name);
}
} }
}
focusClear($event: any): void { focusClear($event: any): void {
if (this.policyId < 0 && this.firstClick === 0) { if (this.policyId < 0 && this.firstClick === 0) {
if ($event && $event.target && $event.target['value']) { if ($event && $event.target && $event.target["value"]) {
$event.target['value'] = ''; $event.target["value"] = "";
} }
this.firstClick ++; this.firstClick++;
} }
} }
@ -339,18 +371,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.selectedProjectList = []; this.selectedProjectList = [];
} }
selectedProjectName(projectName: string) { selectedProjectName(projectName: string) {
this.noSelectedProject = false; this.noSelectedProject = false;
let pro: Project = this.selectedProjectList.find(data => data.name === projectName); let pro: Project = this.selectedProjectList.find(
this.setProject([pro]); data => data.name === projectName
this.selectedProjectList = []; );
this.noProjectInfo = ""; this.setProject([pro]);
} this.selectedProjectList = [];
this.noProjectInfo = "";
}
selectedProject(project: Project): void { selectedProject(project: Project): void {
if (!project) { if (!project) {
this.noSelectedProject = true; this.noSelectedProject = true;
}else { } else {
this.noSelectedProject = false; this.noSelectedProject = false;
this.setProject([project]); this.setProject([project]);
} }
@ -358,16 +392,21 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
addNewFilter(): void { addNewFilter(): void {
if (this.filterCount === 0) { if (this.filterCount === 0) {
this.filterListData.push(this.baseFilterData(this.filterSelect[0], this.filterSelect.slice(), true)); this.filterListData.push(
this.baseFilterData(
this.filterSelect[0],
this.filterSelect.slice(),
true
)
);
this.filters.push(this.initFilter(this.filterSelect[0])); this.filters.push(this.initFilter(this.filterSelect[0]));
} else {
}else {
let nameArr: string[] = this.filterSelect.slice(); let nameArr: string[] = this.filterSelect.slice();
this.filterListData.forEach(data => { this.filterListData.forEach(data => {
nameArr.splice(nameArr.indexOf(data.name), 1); nameArr.splice(nameArr.indexOf(data.name), 1);
}); });
// when add a new filter,the filterListData should change the options // when add a new filter,the filterListData should change the options
this.filterListData.filter((data) => { this.filterListData.filter(data => {
data.options.splice(data.options.indexOf(nameArr[0]), 1); data.options.splice(data.options.indexOf(nameArr[0]), 1);
}); });
this.filterListData.push(this.baseFilterData(nameArr[0], nameArr, true)); this.filterListData.push(this.baseFilterData(nameArr[0], nameArr, true));
@ -395,14 +434,14 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
} }
}); });
} }
const control = <FormArray>this.ruleForm.controls['filters']; const control = <FormArray>this.ruleForm.controls["filters"];
control.removeAt(i); control.removeAt(i);
} }
} }
selectTrigger($event: any): void { selectTrigger($event: any): void {
if ($event && $event.target && $event.target['value']) { if ($event && $event.target && $event.target["value"]) {
let val: string = $event.target['value']; let val: string = $event.target["value"];
if (val === this.triggerNames[2]) { if (val === this.triggerNames[2]) {
this.isScheduleOpt = true; this.isScheduleOpt = true;
this.isImmediate = false; this.isImmediate = false;
@ -420,14 +459,14 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
// Replication Schedule select value exchange // Replication Schedule select value exchange
selectSchedule($event: any): void { selectSchedule($event: any): void {
if ($event && $event.target && $event.target['value']) { if ($event && $event.target && $event.target["value"]) {
switch ($event.target['value']) { switch ($event.target["value"]) {
case this.scheduleNames[1]: case this.scheduleNames[1]:
this.weeklySchedule = true; this.weeklySchedule = true;
this.ruleForm.patchValue({ this.ruleForm.patchValue({
trigger: { trigger: {
schedule_param: { schedule_param: {
weekday: 1, weekday: 1
} }
} }
}); });
@ -440,11 +479,11 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
} }
checkRuleName(): void { checkRuleName(): void {
let ruleName: string = this.ruleForm.controls['name'].value; let ruleName: string = this.ruleForm.controls["name"].value;
if (ruleName) { if (ruleName) {
this.nameChecker.next(ruleName); this.nameChecker.next(ruleName);
} else { } else {
this.ruleNameTooltip = 'TOOLTIP.EMPTY'; this.ruleNameTooltip = "TOOLTIP.EMPTY";
} }
} }
@ -454,7 +493,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
opt.splice(opt.indexOf(filter.kind), 1); opt.splice(opt.indexOf(filter.kind), 1);
}); });
filters.forEach((filter: any) => { filters.forEach((filter: any) => {
let option: string [] = opt.slice(); let option: string[] = opt.slice();
option.unshift(filter.kind); option.unshift(filter.kind);
this.filterListData.push(this.baseFilterData(filter.kind, option, true)); this.filterListData.push(this.baseFilterData(filter.kind, option, true));
}); });
@ -465,41 +504,49 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
} }
updateTrigger(trigger: any) { updateTrigger(trigger: any) {
if (trigger['schedule_param']) { if (trigger["schedule_param"]) {
this.isScheduleOpt = true; this.isScheduleOpt = true;
this.isImmediate = false; this.isImmediate = false;
trigger['schedule_param']['offtime'] = this.getOfftime(trigger['schedule_param']['offtime']); trigger["schedule_param"]["offtime"] = this.getOfftime(
if (trigger['schedule_param']['weekday']) { trigger["schedule_param"]["offtime"]
);
if (trigger["schedule_param"]["weekday"]) {
this.weeklySchedule = true; this.weeklySchedule = true;
}else { } else {
// set default // set default
trigger['schedule_param']['weekday'] = 1; trigger["schedule_param"]["weekday"] = 1;
} }
}else { } else {
if (trigger['kind'] === this.triggerNames[0]) { if (trigger["kind"] === this.triggerNames[0]) {
this.isImmediate = false; this.isImmediate = false;
} }
if (trigger['kind'] === this.triggerNames[1]) { if (trigger["kind"] === this.triggerNames[1]) {
this.isImmediate = true; this.isImmediate = true;
} }
trigger['schedule_param'] = { type: this.scheduleNames[0], trigger["schedule_param"] = {
type: this.scheduleNames[0],
weekday: this.weekly[0], weekday: this.weekly[0],
offtime: '08:00'}; offtime: "08:00"
};
} }
return trigger; return trigger;
} }
setTriggerVaule(trigger: any) { setTriggerVaule(trigger: any) {
if (!this.isScheduleOpt) { if (!this.isScheduleOpt) {
delete trigger['schedule_param']; delete trigger["schedule_param"];
return trigger; return trigger;
}else { } else {
if (!this.weeklySchedule) { if (!this.weeklySchedule) {
delete trigger['schedule_param']['weekday']; delete trigger["schedule_param"]["weekday"];
}else { } else {
trigger['schedule_param']['weekday'] = +trigger['schedule_param']['weekday']; trigger["schedule_param"]["weekday"] = +trigger["schedule_param"][
"weekday"
];
} }
trigger['schedule_param']['offtime'] = this.setOfftime(trigger['schedule_param']['offtime']); trigger["schedule_param"]["offtime"] = this.setOfftime(
trigger["schedule_param"]["offtime"]
);
return trigger; return trigger;
} }
} }
@ -514,29 +561,35 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
let copyRuleForm: ReplicationRule = this.ruleForm.value; let copyRuleForm: ReplicationRule = this.ruleForm.value;
copyRuleForm.trigger = this.setTriggerVaule(copyRuleForm.trigger); copyRuleForm.trigger = this.setTriggerVaule(copyRuleForm.trigger);
if (this.policyId < 0) { if (this.policyId < 0) {
this.repService.createReplicationRule(copyRuleForm) this.repService
.then(() => { .createReplicationRule(copyRuleForm)
this.translateService.get('REPLICATION.CREATED_SUCCESS') .then(() => {
.subscribe(res => this.errorHandler.info(res)); this.translateService
this.inProgress = false; .get("REPLICATION.CREATED_SUCCESS")
this.reload.emit(true); .subscribe(res => this.errorHandler.info(res));
this.close(); this.inProgress = false;
}).catch((error: any) => { this.reload.emit(true);
this.inProgress = false; this.close();
this.inlineAlert.showInlineError(error); })
}); .catch((error: any) => {
this.inProgress = false;
this.inlineAlert.showInlineError(error);
});
} else { } else {
this.repService.updateReplicationRule(this.policyId, this.ruleForm.value) this.repService
.then(() => { .updateReplicationRule(this.policyId, this.ruleForm.value)
this.translateService.get('REPLICATION.UPDATED_SUCCESS') .then(() => {
.subscribe(res => this.errorHandler.info(res)); this.translateService
this.inProgress = false; .get("REPLICATION.UPDATED_SUCCESS")
this.reload.emit(true); .subscribe(res => this.errorHandler.info(res));
this.close(); this.inProgress = false;
}).catch((error: any) => { this.reload.emit(true);
this.inProgress = false; this.close();
this.inlineAlert.showInlineError(error); })
}); .catch((error: any) => {
this.inProgress = false;
this.inlineAlert.showInlineError(error);
});
} }
} }
@ -559,34 +612,39 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
this.noProjectInfo = ""; this.noProjectInfo = "";
this.noEndpointInfo = ""; this.noEndpointInfo = "";
if (this.targetList.length === 0) { if (this.targetList.length === 0) {
this.noEndpointInfo = 'REPLICATION.NO_ENDPOINT_INFO'; this.noEndpointInfo = "REPLICATION.NO_ENDPOINT_INFO";
} }
if (this.projectList.length === 0 && !this.projectName) { if (this.projectList.length === 0 && !this.projectName) {
this.noProjectInfo = 'REPLICATION.NO_PROJECT_INFO'; this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
} }
if (ruleId) { if (ruleId) {
this.policyId = +ruleId; this.policyId = +ruleId;
this.headerTitle = 'REPLICATION.EDIT_POLICY_TITLE'; this.headerTitle = "REPLICATION.EDIT_POLICY_TITLE";
toPromise(this.repService.getReplicationRule(ruleId)) toPromise(this.repService.getReplicationRule(ruleId))
.then((response) => { .then(response => {
this.copyUpdateForm = Object.assign({}, response); this.copyUpdateForm = Object.assign({}, response);
// set filter value is [] if callback fiter value is null. // set filter value is [] if callback fiter value is null.
this.copyUpdateForm.filters = response.filters ? response.filters : []; this.copyUpdateForm.filters = response.filters
this.updateForm(response); ? response.filters
}).catch((error: any) => { : [];
this.inlineAlert.showInlineError(error); this.updateForm(response);
}); })
}else { .catch((error: any) => {
this.headerTitle = 'REPLICATION.ADD_POLICY'; this.inlineAlert.showInlineError(error);
if (this.projectId) { });
this.setProject([{project_id: this.projectId, name: this.projectName}]); } else {
this.noSelectedProject = false; this.headerTitle = "REPLICATION.ADD_POLICY";
} if (this.projectId) {
this.setProject([
this.copyUpdateForm = Object.assign({}, this.ruleForm.value); { project_id: this.projectId, name: this.projectName }
]);
this.noSelectedProject = false;
} }
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
} }
}
close(): void { close(): void {
this.createEditRuleOpened = false; this.createEditRuleOpened = false;
@ -599,8 +657,10 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
onCancel(): void { onCancel(): void {
if (this.hasFormChange()) { if (this.hasFormChange()) {
this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' }); this.inlineAlert.showInlineConfirmation({
}else { message: "ALERT.FORM_CHANGE_CONFIRMATION"
});
} else {
this.close(); this.close();
} }
} }
@ -611,9 +671,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
// UTC time // UTC time
public getOfftime(daily_time: any): string { public getOfftime(daily_time: any): string {
let timeOffset = 0; // seconds let timeOffset = 0; // seconds
if (daily_time && typeof daily_time === 'number') { if (daily_time && typeof daily_time === "number") {
timeOffset = +daily_time; timeOffset = +daily_time;
} }
@ -631,27 +690,29 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
// To time string // To time string
let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS); let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS);
let minutes: number = Math.floor((timeOffset - hours * ONE_HOUR_SECONDS) / 60); let minutes: number = Math.floor(
(timeOffset - hours * ONE_HOUR_SECONDS) / 60
);
let timeStr: string = '' + hours; let timeStr: string = "" + hours;
if (hours < 10) { if (hours < 10) {
timeStr = '0' + timeStr; timeStr = "0" + timeStr;
} }
if (minutes < 10) { if (minutes < 10) {
timeStr += ':0'; timeStr += ":0";
} else { } else {
timeStr += ':'; timeStr += ":";
} }
timeStr += minutes; timeStr += minutes;
return timeStr; return timeStr;
} }
public setOfftime(v: string) { public setOfftime(v: string) {
if (!v || v === '') { if (!v || v === "") {
return; return;
} }
let values: string[] = v.split(':'); let values: string[] = v.split(":");
if (!values || values.length !== 2) { if (!values || values.length !== 2) {
return; return;
} }
@ -679,13 +740,21 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
if (!ruleValue || !this.copyUpdateForm) { if (!ruleValue || !this.copyUpdateForm) {
return changes; return changes;
} }
for (let prop in ruleValue) { for (let prop of Object.keys(ruleValue)) {
let field: any = this.copyUpdateForm[prop]; let field: any = this.copyUpdateForm[prop];
if (!compareValue(field, ruleValue[prop])) { if (!compareValue(field, ruleValue[prop])) {
if (ruleValue[prop][0] && ruleValue[prop][0].project_id && (ruleValue[prop][0].project_id === field[0].project_id)) { if (
ruleValue[prop][0] &&
ruleValue[prop][0].project_id &&
ruleValue[prop][0].project_id === field[0].project_id
) {
break; break;
} }
if (ruleValue[prop][0] && ruleValue[prop][0].id && (ruleValue[prop][0].id === field[0].id)) { if (
ruleValue[prop][0] &&
ruleValue[prop][0].id &&
ruleValue[prop][0].id === field[0].id
) {
break; break;
} }
changes[prop] = ruleValue[prop]; changes[prop] = ruleValue[prop];
@ -696,12 +765,11 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
// Trim string value // Trim string value
if (typeof field === "string") { if (typeof field === "string") {
changes[prop] = ('' + changes[prop]).trim(); changes[prop] = ("" + changes[prop]).trim();
} }
} }
} }
return changes; return changes;
} }
}
}

View File

@ -1,7 +1,7 @@
import { Type } from '@angular/core'; import { Type } from "@angular/core";
import { CreateEditRuleComponent } from './create-edit-rule.component'; import { CreateEditRuleComponent } from "./create-edit-rule.component";
export const CREATE_EDIT_RULE_DIRECTIVES: Type<any>[] = [ export const CREATE_EDIT_RULE_DIRECTIVES: Type<any>[] = [
CreateEditRuleComponent CreateEditRuleComponent
]; ];

View File

@ -11,40 +11,49 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Directive, OnChanges, Input, SimpleChanges } from '@angular/core'; import { Directive, OnChanges, Input, SimpleChanges } from "@angular/core";
import { NG_VALIDATORS, Validator, Validators, ValidatorFn, AbstractControl } from '@angular/forms'; import {
NG_VALIDATORS,
Validator,
Validators,
ValidatorFn,
AbstractControl
} from "@angular/forms";
@Directive({ @Directive({
selector: '[dateValidator]', selector: "[dateValidator]",
providers: [{provide: NG_VALIDATORS, useExisting: DateValidatorDirective, multi: true}] providers: [
{ provide: NG_VALIDATORS, useExisting: DateValidatorDirective, multi: true }
]
}) })
export class DateValidatorDirective implements Validator, OnChanges { export class DateValidatorDirective implements Validator, OnChanges {
@Input() dateValidator: string; @Input() dateValidator: string;
private valFn = Validators.nullValidator; private valFn = Validators.nullValidator;
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
const change = changes['dateValidator']; const change = changes["dateValidator"];
if (change) { if (change) {
this.valFn = dateValidator(); this.valFn = dateValidator();
} else { } else {
this.valFn = Validators.nullValidator; this.valFn = Validators.nullValidator;
} }
} }
validate(control: AbstractControl): {[key: string]: any} { validate(control: AbstractControl): { [key: string]: any } {
return this.valFn(control) || Validators.nullValidator; return this.valFn(control) || Validators.nullValidator;
} }
} }
export function dateValidator(): ValidatorFn { export function dateValidator(): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => { return (control: AbstractControl): { [key: string]: any } => {
let controlValue = control.value; let controlValue = control.value;
let valid = true; let valid = true;
if(controlValue) { if (controlValue) {
const regYMD=/^(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/g; const regYMD = /^(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/g;
const regDMY=/^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d$/g; const regDMY = /^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d$/g;
valid = (regYMD.test(controlValue) || regDMY.test(controlValue)); valid = regYMD.test(controlValue) || regDMY.test(controlValue);
} }
return valid ? Validators.nullValidator : {'dateValidator': { value: controlValue }}; return valid
? Validators.nullValidator
: { dateValidator: { value: controlValue } };
}; };
} }

View File

@ -1,17 +1,22 @@
import {Component, Input, Output, EventEmitter, ViewChild, OnChanges} from '@angular/core'; import {
import { NgModel } from '@angular/forms'; Component,
Input,
Output,
EventEmitter,
ViewChild,
OnChanges
} from "@angular/core";
import { NgModel } from "@angular/forms";
@Component({ @Component({
selector: 'hbr-datetime', selector: "hbr-datetime",
templateUrl: './datetime-picker.component.html' templateUrl: "./datetime-picker.component.html"
}) })
export class DatePickerComponent implements OnChanges{ export class DatePickerComponent implements OnChanges {
@Input() dateInput: string; @Input() dateInput: string;
@Input() oneDayOffset: boolean; @Input() oneDayOffset: boolean;
@ViewChild('searchTime') @ViewChild("searchTime") searchTime: NgModel;
searchTime: NgModel;
@Output() search = new EventEmitter<string>(); @Output() search = new EventEmitter<string>();
@ -20,26 +25,37 @@ export class DatePickerComponent implements OnChanges{
} }
get dateInvalid(): boolean { get dateInvalid(): boolean {
return (this.searchTime.errors && this.searchTime.errors.dateValidator && (this.searchTime.dirty || this.searchTime.touched)) || false; return (
(this.searchTime.errors &&
this.searchTime.errors.dateValidator &&
(this.searchTime.dirty || this.searchTime.touched)) ||
false
);
} }
convertDate(strDate: string): string { convertDate(strDate: string): string {
if(/^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d$/.test(strDate)) { if (
/^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d$/.test(
strDate
)
) {
let parts = strDate.split(/[-\/]/); let parts = strDate.split(/[-\/]/);
strDate = parts[2] /*Year*/ + '-' +parts[1] /*Month*/ + '-' + parts[0] /*Date*/; strDate =
parts[2] /*Year*/ + "-" + parts[1] /*Month*/ + "-" + parts[0] /*Date*/;
} }
return strDate; return strDate;
} }
doSearch() { doSearch() {
let searchTerm: string = ''; let searchTerm: string = "";
if(this.searchTime.valid && this.dateInput) { if (this.searchTime.valid && this.dateInput) {
let timestamp: number = new Date(this.convertDate(this.searchTime.value)).getTime() / 1000; let timestamp: number =
if(this.oneDayOffset) { new Date(this.convertDate(this.searchTime.value)).getTime() / 1000;
if (this.oneDayOffset) {
timestamp += 3600 * 24; timestamp += 3600 * 24;
} }
searchTerm = timestamp.toString(); searchTerm = timestamp.toString();
} }
this.search.emit(searchTerm); this.search.emit(searchTerm);
} }
} }

View File

@ -1,8 +1,8 @@
import { Type } from '@angular/core'; import { Type } from "@angular/core";
import { DatePickerComponent } from './datetime-picker.component'; import { DatePickerComponent } from "./datetime-picker.component";
import { DateValidatorDirective } from './date-validator.directive'; import { DateValidatorDirective } from "./date-validator.directive";
export const DATETIME_PICKER_DIRECTIVES: Type<any>[] = [ export const DATETIME_PICKER_DIRECTIVES: Type<any>[] = [
DatePickerComponent, DatePickerComponent,
DateValidatorDirective DateValidatorDirective
]; ];

View File

@ -3,7 +3,7 @@
<div> <div>
<div class="row flex-items-xs-between rightPos"> <div class="row flex-items-xs-between rightPos">
<div class="flex-items-xs-middle option-right"> <div class="flex-items-xs-middle option-right">
<hbr-filter [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_TARGETS_PLACEHOLDER" | translate}}' (filter)="doSearchTargets($event)" [currentValue]="targetName"></hbr-filter> <hbr-filter [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_TARGETS_PLACEHOLDER" | translate}}' (filterEvt)="doSearchTargets($event)" [currentValue]="targetName"></hbr-filter>
<span class="refresh-btn" (click)="refreshTargets()"> <span class="refresh-btn" (click)="refreshTargets()">
<clr-icon shape="refresh"></clr-icon> <clr-icon shape="refresh"></clr-icon>
</span> </span>

View File

@ -1,76 +1,80 @@
import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing'; import { ComponentFixture, TestBed, async } from "@angular/core/testing";
import { By } from '@angular/platform-browser'; import { By } from "@angular/platform-browser";
import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { DebugElement } from '@angular/core'; import { DebugElement } from "@angular/core";
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from "../shared/shared.module";
import { EndpointComponent } from './endpoint.component'; import { EndpointComponent } from "./endpoint.component";
import { FilterComponent } from '../filter/filter.component'; import { FilterComponent } from "../filter/filter.component";
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { CreateEditEndpointComponent } from '../create-edit-endpoint/create-edit-endpoint.component'; import { CreateEditEndpointComponent } from "../create-edit-endpoint/create-edit-endpoint.component";
import { InlineAlertComponent } from '../inline-alert/inline-alert.component'; import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from "../error-handler/error-handler";
import { Endpoint } from '../service/interface'; import { Endpoint } from "../service/interface";
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service'; import {
import { IServiceConfig, SERVICE_CONFIG } from '../service.config'; EndpointService,
EndpointDefaultService
} from "../service/endpoint.service";
import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
import { click } from '../utils'; import { click } from "../utils";
describe('EndpointComponent (inline template)', () => {
describe("EndpointComponent (inline template)", () => {
let mockData: Endpoint[] = [ let mockData: Endpoint[] = [
{ {
"id": 1, id: 1,
"endpoint": "https://10.117.4.151", endpoint: "https://10.117.4.151",
"name": "target_01", name: "target_01",
"username": "admin", username: "admin",
"password": "", password: "",
"insecure": true, insecure: true,
"type": 0 type: 0
}, },
{ {
"id": 2, id: 2,
"endpoint": "https://10.117.5.142", endpoint: "https://10.117.5.142",
"name": "target_02", name: "target_02",
"username": "AAA", username: "AAA",
"password": "", password: "",
"insecure": false, insecure: false,
"type": 0 type: 0
}, },
{ {
"id": 3, id: 3,
"endpoint": "https://101.1.11.111", endpoint: "https://101.1.11.111",
"name": "target_03", name: "target_03",
"username": "admin", username: "admin",
"password": "", password: "",
"insecure": false, insecure: false,
"type": 0 type: 0
}, },
{ {
"id": 4, id: 4,
"endpoint": "http://4.4.4.4", endpoint: "http://4.4.4.4",
"name": "target_04", name: "target_04",
"username": "", username: "",
"password": "", password: "",
"insecure": false, insecure: false,
"type": 0 type: 0
} }
]; ];
let mockOne: Endpoint[] = [{ let mockOne: Endpoint[] = [
"id": 1, {
"endpoint": "https://10.117.4.151", id: 1,
"name": "target_01", endpoint: "https://10.117.4.151",
"username": "admin", name: "target_01",
"password": "", username: "admin",
"insecure": false, password: "",
"type": 0 insecure: false,
}]; type: 0
}
];
let comp: EndpointComponent; let comp: EndpointComponent;
let fixture: ComponentFixture<EndpointComponent>; let fixture: ComponentFixture<EndpointComponent>;
let config: IServiceConfig = { let config: IServiceConfig = {
systemInfoEndpoint: '/api/endpoints/testing' systemInfoEndpoint: "/api/endpoints/testing"
}; };
let endpointService: EndpointService; let endpointService: EndpointService;
@ -79,16 +83,14 @@ describe('EndpointComponent (inline template)', () => {
let spyOne: jasmine.Spy; let spyOne: jasmine.Spy;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [SharedModule, NoopAnimationsModule],
SharedModule, declarations: [
NoopAnimationsModule FilterComponent,
ConfirmationDialogComponent,
CreateEditEndpointComponent,
InlineAlertComponent,
EndpointComponent
], ],
declarations: [
FilterComponent,
ConfirmationDialogComponent,
CreateEditEndpointComponent,
InlineAlertComponent,
EndpointComponent ],
providers: [ providers: [
ErrorHandler, ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config }, { provide: SERVICE_CONFIG, useValue: config },
@ -97,87 +99,96 @@ describe('EndpointComponent (inline template)', () => {
}); });
})); }));
beforeEach(()=>{ beforeEach(() => {
fixture = TestBed.createComponent(EndpointComponent); fixture = TestBed.createComponent(EndpointComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
endpointService = fixture.debugElement.injector.get(EndpointService); endpointService = fixture.debugElement.injector.get(EndpointService);
spy = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockData)); spy = spyOn(endpointService, "getEndpoints").and.returnValues(
spyOnRules = spyOn(endpointService, 'getEndpointWithReplicationRules').and.returnValue([]); Promise.resolve(mockData)
spyOne = spyOn(endpointService, 'getEndpoint').and.returnValue(Promise.resolve(mockOne[0])); );
spyOnRules = spyOn(
endpointService,
"getEndpointWithReplicationRules"
).and.returnValue([]);
spyOne = spyOn(endpointService, "getEndpoint").and.returnValue(
Promise.resolve(mockOne[0])
);
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should retrieve endpoint data', () => { it("should retrieve endpoint data", () => {
fixture.detectChanges(); fixture.detectChanges();
expect(spy.calls.any()).toBeTruthy(); expect(spy.calls.any()).toBeTruthy();
}); });
it('should endpoint be initialized', () => { it("should endpoint be initialized", () => {
fixture.detectChanges(); fixture.detectChanges();
expect(config.systemInfoEndpoint).toEqual('/api/endpoints/testing'); expect(config.systemInfoEndpoint).toEqual("/api/endpoints/testing");
}); });
it('should open create endpoint modal', async(() => { it("should open create endpoint modal", async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
comp.editTargets(mockOne); comp.editTargets(mockOne);
fixture.detectChanges(); fixture.detectChanges();
expect(comp.target.name).toEqual('target_01'); expect(comp.target.name).toEqual("target_01");
}); });
})); }));
it('should filter endpoints by keyword', async(() => { it("should filter endpoints by keyword", async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
comp.doSearchTargets('target_02'); comp.doSearchTargets("target_02");
fixture.detectChanges(); fixture.detectChanges();
expect(comp.targets.length).toEqual(1); expect(comp.targets.length).toEqual(1);
}); });
})); }));
it('should render data', async(()=>{ it("should render data", async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(By.css('datagrid-cell')); let de: DebugElement = fixture.debugElement.query(
By.css("datagrid-cell")
);
expect(de).toBeTruthy(); expect(de).toBeTruthy();
let el: HTMLElement = de.nativeElement; let el: HTMLElement = de.nativeElement;
expect(el.textContent).toEqual('target_01'); expect(el.textContent).toEqual("target_01");
}); });
})); }));
it('should open creation endpoint', async(()=>{ it("should open creation endpoint", async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
let de: DebugElement = fixture.debugElement.query(By.css('btn-link')); let de: DebugElement = fixture.debugElement.query(By.css("btn-link"));
expect(de).toBeTruthy(); expect(de).toBeTruthy();
fixture.detectChanges(); fixture.detectChanges();
click(de); click(de);
fixture.detectChanges(); fixture.detectChanges();
let deInput: DebugElement = fixture.debugElement.query(By.css('input')); let deInput: DebugElement = fixture.debugElement.query(By.css("input"));
expect(deInput).toBeTruthy(); expect(deInput).toBeTruthy();
}); });
})); }));
it("should open to edit existing endpoint", async(() => {
it('should open to edit existing endpoint', async(()=>{
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
let de: DebugElement = fixture.debugElement.query(del=>del.classes['action-item']); let de: DebugElement = fixture.debugElement.query(
del => del.classes["action-item"]
);
expect(de).toBeTruthy(); expect(de).toBeTruthy();
fixture.detectChanges(); fixture.detectChanges();
click(de); click(de);
fixture.detectChanges(); fixture.detectChanges();
let deInput: DebugElement = fixture.debugElement.query(By.css('input')); let deInput: DebugElement = fixture.debugElement.query(By.css("input"));
expect(deInput).toBeTruthy(); expect(deInput).toBeTruthy();
let elInput: HTMLElement = deInput.nativeElement; let elInput: HTMLElement = deInput.nativeElement;
expect(elInput).toBeTruthy(); expect(elInput).toBeTruthy();
expect(elInput.textContent).toEqual('target_01'); expect(elInput.textContent).toEqual("target_01");
}); });
})); }));
});
});

View File

@ -11,214 +11,239 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; import {
import { Endpoint, ReplicationRule } from '../service/interface'; Component,
import { EndpointService } from '../service/endpoint.service'; OnInit,
OnDestroy,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef
} from "@angular/core";
import { Endpoint } from "../service/interface";
import { EndpointService } from "../service/endpoint.service";
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from "@ngx-translate/core";
import { ErrorHandler } from '../error-handler/index'; import { ErrorHandler } from "../error-handler/index";
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message'; import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message'; import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../shared/shared.const'; import {
ConfirmationTargets,
ConfirmationState,
ConfirmationButtons
} from "../shared/shared.const";
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from "rxjs/Subscription";
import { CreateEditEndpointComponent } from '../create-edit-endpoint/create-edit-endpoint.component'; import { CreateEditEndpointComponent } from "../create-edit-endpoint/create-edit-endpoint.component";
import { toPromise, CustomComparator } from '../utils'; import { toPromise, CustomComparator } from "../utils";
import { State, Comparator } from 'clarity-angular'; import { Comparator } from "clarity-angular";
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message"; import {
import {Observable} from "rxjs/Observable"; BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
import { Observable } from "rxjs/Observable";
@Component({ @Component({
selector: 'hbr-endpoint', selector: "hbr-endpoint",
templateUrl: './endpoint.component.html', templateUrl: "./endpoint.component.html",
styleUrls: ['./endpoint.component.scss'], styleUrls: ["./endpoint.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class EndpointComponent implements OnInit, OnDestroy { export class EndpointComponent implements OnInit, OnDestroy {
@ViewChild(CreateEditEndpointComponent)
createEditEndpointComponent: CreateEditEndpointComponent;
@ViewChild(CreateEditEndpointComponent) @ViewChild("confirmationDialog")
createEditEndpointComponent: CreateEditEndpointComponent; confirmationDialogComponent: ConfirmationDialogComponent;
targets: Endpoint[];
target: Endpoint;
@ViewChild('confirmationDialog') targetName: string;
confirmationDialogComponent: ConfirmationDialogComponent; subscription: Subscription;
targets: Endpoint[]; loading: boolean = false;
target: Endpoint;
targetName: string; creationTimeComparator: Comparator<Endpoint> = new CustomComparator<Endpoint>(
subscription: Subscription; "creation_time",
"date"
);
loading: boolean = false; timerHandler: any;
selectedRow: Endpoint[] = [];
batchDelectionInfos: BatchInfo[] = [];
creationTimeComparator: Comparator<Endpoint> = new CustomComparator<Endpoint>('creation_time', 'date'); get initEndpoint(): Endpoint {
return {
endpoint: "",
name: "",
username: "",
password: "",
insecure: false,
type: 0
};
}
timerHandler: any; constructor(
selectedRow: Endpoint[] = []; private endpointService: EndpointService,
batchDelectionInfos: BatchInfo[] = []; private errorHandler: ErrorHandler,
private translateService: TranslateService,
private ref: ChangeDetectorRef
) {
this.forceRefreshView(1000);
}
get initEndpoint(): Endpoint { ngOnInit(): void {
return { this.targetName = "";
endpoint: "", this.retrieve();
name: "", }
username: "",
password: "", ngOnDestroy(): void {
insecure: false, if (this.subscription) {
type: 0 this.subscription.unsubscribe();
};
} }
}
selectedChange(): void {
this.forceRefreshView(5000);
}
constructor( retrieve(): void {
private endpointService: EndpointService, this.loading = true;
private errorHandler: ErrorHandler, this.selectedRow = [];
private translateService: TranslateService, toPromise<Endpoint[]>(this.endpointService.getEndpoints(this.targetName))
private ref: ChangeDetectorRef) { .then(targets => {
this.targets = targets || [];
this.forceRefreshView(1000); this.forceRefreshView(1000);
} this.loading = false;
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
ngOnInit(): void { doSearchTargets(targetName: string) {
this.targetName = ''; this.targetName = targetName;
this.retrieve(); this.retrieve();
} }
ngOnDestroy(): void { refreshTargets() {
if (this.subscription) { this.retrieve();
this.subscription.unsubscribe(); }
reload($event: any) {
this.targetName = "";
this.retrieve();
}
openModal() {
this.createEditEndpointComponent.openCreateEditTarget(true);
this.target = this.initEndpoint;
}
editTargets(targets: Endpoint[]) {
if (targets && targets.length === 1) {
let target = targets[0];
let editable = true;
if (!target.id) {
return;
}
let id: number | string = target.id;
this.createEditEndpointComponent.openCreateEditTarget(editable, id);
}
}
deleteTargets(targets: Endpoint[]) {
if (targets && targets.length) {
let targetNames: string[] = [];
this.batchDelectionInfos = [];
targets.forEach(target => {
targetNames.push(target.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = target.name;
this.batchDelectionInfos.push(initBatchMessage);
});
let deletionMessage = new ConfirmationMessage(
"REPLICATION.DELETION_TITLE_TARGET",
"REPLICATION.DELETION_SUMMARY_TARGET",
targetNames.join(", ") || "",
targets,
ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL
);
this.confirmationDialogComponent.open(deletionMessage);
}
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (
message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED
) {
let targetLists: Endpoint[] = message.data;
if (targetLists && targetLists.length) {
let promiseLists: any[] = [];
targetLists.forEach(target => {
promiseLists.push(this.delOperate(target.id, target.name));
});
Promise.all(promiseLists).then(item => {
this.selectedRow = [];
this.reload(true);
this.forceRefreshView(2000);
});
}
}
}
delOperate(id: number | string, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<number>(this.endpointService.deleteEndpoint(id))
.then(response => {
this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => {
findedList = BathInfoChanges(findedList, res);
});
})
.catch(error => {
if (error && error.status === 412) {
Observable.forkJoin(
this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get(
"DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED"
)
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
});
} else {
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
} }
});
}
// Forcely refresh the view
forceRefreshView(duration: number): void {
// Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
} }
selectedChange(): void { this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
this.forceRefreshView(5000); setTimeout(() => {
} if (this.timerHandler) {
clearInterval(this.timerHandler);
retrieve(): void { this.timerHandler = null;
this.loading = true; }
this.selectedRow = []; }, duration);
toPromise<Endpoint[]>(this.endpointService }
.getEndpoints(this.targetName)) }
.then(
targets => {
this.targets = targets || [];
this.forceRefreshView(1000);
this.loading = false;
}).catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
doSearchTargets(targetName: string) {
this.targetName = targetName;
this.retrieve();
}
refreshTargets() {
this.retrieve();
}
reload($event: any) {
this.targetName = '';
this.retrieve();
}
openModal() {
this.createEditEndpointComponent.openCreateEditTarget(true);
this.target = this.initEndpoint;
}
editTargets(targets: Endpoint[]) {
if (targets && targets.length === 1) {
let target = targets[0];
let editable = true;
if (!target.id) {
return;
}
let id: number | string = target.id;
this.createEditEndpointComponent.openCreateEditTarget(editable, id);
}
}
deleteTargets(targets: Endpoint[]) {
if (targets && targets.length) {
let targetNames: string[] = [];
this.batchDelectionInfos = [];
targets.forEach(target => {
targetNames.push(target.name);
let initBatchMessage = new BatchInfo ();
initBatchMessage.name = target.name;
this.batchDelectionInfos.push(initBatchMessage);
});
let deletionMessage = new ConfirmationMessage(
'REPLICATION.DELETION_TITLE_TARGET',
'REPLICATION.DELETION_SUMMARY_TARGET',
targetNames.join(', ') || '',
targets,
ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL);
this.confirmationDialogComponent.open(deletionMessage);
}
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED) {
let targetLists: Endpoint[] = message.data;
if (targetLists && targetLists.length) {
let promiseLists: any[] = [];
targetLists.forEach(target => {
promiseLists.push(this.delOperate(target.id, target.name));
})
Promise.all(promiseLists).then((item) => {
this.selectedRow = [];
this.reload(true);
this.forceRefreshView(2000);
});
}
}
}
delOperate(id: number | string, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<number>(this.endpointService
.deleteEndpoint(id))
.then(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => {
findedList = BathInfoChanges(findedList, res);
});
}).catch(
error => {
if (error && error.status === 412) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
} else {
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
}
});
}
//Forcely refresh the view
forceRefreshView(duration: number): void {
//Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
}
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => {
if (this.timerHandler) {
clearInterval(this.timerHandler);
this.timerHandler = null;
}
}, duration);
}
}

View File

@ -1,7 +1,4 @@
import { Type } from '@angular/core'; import { Type } from "@angular/core";
import { EndpointComponent } from './endpoint.component'; import { EndpointComponent } from "./endpoint.component";
export const ENDPOINT_DIRECTIVES: Type<any>[] = [EndpointComponent];
export const ENDPOINT_DIRECTIVES: Type<any>[] = [
EndpointComponent
];

View File

@ -2,69 +2,69 @@ import { Injectable } from "@angular/core";
/** /**
* Declare interface for error handling * Declare interface for error handling
* *
* @export * @export
* @abstract * @abstract
* @class ErrorHandler * @class ErrorHandler
*/ */
export abstract class ErrorHandler { export abstract class ErrorHandler {
/** /**
* Send message with error level * Send message with error level
* *
* @abstract * @abstract
* @param {*} error * @param {*} error
* *
* @memberOf ErrorHandler * @memberOf ErrorHandler
*/ */
abstract error(error: any): void; abstract error(error: any): void;
/** /**
* Send message with warning level * Send message with warning level
* *
* @abstract * @abstract
* @param {*} warning * @param {*} warning
* *
* @memberOf ErrorHandler * @memberOf ErrorHandler
*/ */
abstract warning(warning: any): void; abstract warning(warning: any): void;
/** /**
* Send message with info level * Send message with info level
* *
* @abstract * @abstract
* @param {*} info * @param {*} info
* *
* @memberOf ErrorHandler * @memberOf ErrorHandler
*/ */
abstract info(info: any): void; abstract info(info: any): void;
/** /**
* Handle log message * Handle log message
* *
* @abstract * @abstract
* @param {*} log * @param {*} log
* *
* @memberOf ErrorHandler * @memberOf ErrorHandler
*/ */
abstract log(log: any): void; abstract log(log: any): void;
} }
@Injectable() @Injectable()
export class DefaultErrorHandler extends ErrorHandler { export class DefaultErrorHandler extends ErrorHandler {
public error(error: any): void { public error(error: any): void {
console.error("[Default error handler]: ", error); console.error("[Default error handler]: ", error);
} }
public warning(warning: any): void { public warning(warning: any): void {
console.warn("[Default warning handler]: ", warning); console.warn("[Default warning handler]: ", warning);
} }
public info(info: any): void { public info(info: any): void {
console.info("[Default info handler]: ", info); // tslint:disable-next-line:no-console
} console.info("[Default info handler]: ", info);
}
public log(log: any): void { public log(log: any): void {
console.log("[Default log handler]: ", log); console.log("[Default log handler]: ", log);
} }
} }

View File

@ -1 +1 @@
export * from './error-handler'; export * from "./error-handler";

View File

@ -11,66 +11,60 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; import { Component, Input, Output, OnInit, EventEmitter } from "@angular/core";
import { Subject } from 'rxjs/Subject'; import { Subject } from "rxjs/Subject";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
@Component({ @Component({
selector: 'hbr-filter', selector: "hbr-filter",
templateUrl: './filter.component.html', templateUrl: "./filter.component.html",
styleUrls: ['./filter.component.scss'] styleUrls: ["./filter.component.scss"]
}) })
export class FilterComponent implements OnInit { export class FilterComponent implements OnInit {
placeHolder: string = "";
filterTerms = new Subject<string>();
isExpanded: boolean = false;
placeHolder: string = ""; @Output() private filterEvt = new EventEmitter<string>();
filterTerms = new Subject<string>(); @Output() private openFlag = new EventEmitter<boolean>();
isExpanded: boolean = false;
@Output("filter") private filterEvt = new EventEmitter<string>(); @Input() currentValue: string;
@Output() private openFlag = new EventEmitter<boolean>(); @Input("filterPlaceholder")
public set flPlaceholder(placeHolder: string) {
this.placeHolder = placeHolder;
}
@Input() expandMode: boolean = false;
@Input() withDivider: boolean = false;
@Input() currentValue: string; ngOnInit(): void {
@Input("filterPlaceholder") this.filterTerms
public set flPlaceholder(placeHolder: string) { .debounceTime(500)
this.placeHolder = placeHolder; .subscribe(terms => {
this.filterEvt.emit(terms);
});
}
valueChange(): void {
// Send out filter terms
this.filterTerms.next(this.currentValue.trim());
}
inputFocus(): void {
this.openFlag.emit(this.isExpanded);
}
onClick(): void {
// Only enabled when expandMode is set to false
if (this.expandMode) {
return;
} }
@Input() expandMode: boolean = false; this.isExpanded = !this.isExpanded;
@Input() withDivider: boolean = false; this.openFlag.emit(this.isExpanded);
}
ngOnInit(): void { public get isShowSearchBox(): boolean {
this.filterTerms return this.expandMode || (!this.expandMode && this.isExpanded);
.debounceTime(500) }
//.distinctUntilChanged() }
.subscribe(terms => {
this.filterEvt.emit(terms);
});
}
valueChange(): void {
//Send out filter terms
this.filterTerms.next(this.currentValue.trim());
}
inputFocus(): void {
this.openFlag.emit(this.isExpanded);
}
onClick(): void {
//Only enabled when expandMode is set to false
if(this.expandMode){
return;
}
this.isExpanded = !this.isExpanded;
this.openFlag.emit(this.isExpanded);
}
public get isShowSearchBox(): boolean {
return this.expandMode || (!this.expandMode && this.isExpanded);
}
}

View File

@ -5,4 +5,4 @@ export * from "./filter.component";
export const FILTER_DIRECTIVES: Type<any>[] = [ export const FILTER_DIRECTIVES: Type<any>[] = [
FilterComponent FilterComponent
]; ];

View File

@ -9,19 +9,28 @@
* conditions of the subcomponent's license, as noted in the LICENSE file. * conditions of the subcomponent's license, as noted in the LICENSE file.
*/ */
import { Component, Input, Output, SimpleChanges, ContentChild, ViewChild, ViewChildren, import {
TemplateRef, HostListener, ViewEncapsulation, EventEmitter, AfterViewInit } from '@angular/core'; Component,
import { CancelablePromise } from '../shared/shared.utils'; Input,
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; Output,
import { Subscription } from 'rxjs/Subscription'; ContentChild,
ViewChild,
ViewChildren,
TemplateRef,
HostListener,
ViewEncapsulation,
EventEmitter,
AfterViewInit
} from "@angular/core";
import { Subscription } from "rxjs/Subscription";
import { TranslateService } from "@ngx-translate/core";
import { TranslateService } from '@ngx-translate/core'; import { ScrollPosition } from "../service/interface";
import { ScrollPosition } from '../service/interface'
@Component({ @Component({
selector: 'hbr-gridview', selector: "hbr-gridview",
templateUrl: './grid-view.component.html', templateUrl: "./grid-view.component.html",
styleUrls: ['./grid-view.component.scss'], styleUrls: ["./grid-view.component.scss"],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
/** /**
@ -41,9 +50,9 @@ export class GridViewComponent implements AfterViewInit {
return this.cardStyles[index]; return this.cardStyles[index];
} }
return { return {
opacity: '0', opacity: "0",
overflow: 'hidden' overflow: "hidden"
}; };
}); });
this.cardStyles = newCardStyles; this.cardStyles = newCardStyles;
this._items = value; this._items = value;
@ -51,8 +60,8 @@ export class GridViewComponent implements AfterViewInit {
@Output() loadNextPageEvent = new EventEmitter<any>(); @Output() loadNextPageEvent = new EventEmitter<any>();
@ViewChildren('cardItem') cards: any; @ViewChildren("cardItem") cards: any;
@ViewChild('itemsHolder') itemsHolder: any; @ViewChild("itemsHolder") itemsHolder: any;
@ContentChild(TemplateRef) gridItemTmpl: any; @ContentChild(TemplateRef) gridItemTmpl: any;
_items: any[] = []; _items: any[] = [];
@ -78,7 +87,7 @@ export class GridViewComponent implements AfterViewInit {
preScrollPosition: ScrollPosition = null; preScrollPosition: ScrollPosition = null;
constructor(private translate: TranslateService) { } constructor(private translate: TranslateService) {}
ngAfterViewInit() { ngAfterViewInit() {
this.cards.changes.subscribe(() => { this.cards.changes.subscribe(() => {
@ -91,21 +100,22 @@ export class GridViewComponent implements AfterViewInit {
return this._items; return this._items;
} }
@HostListener('scroll', ['$event']) @HostListener("scroll", ["$event"])
onScroll(event: any) { onScroll(event: any) {
this.preScrollPosition = this.CurrentScrollPosition; this.preScrollPosition = this.CurrentScrollPosition;
this.CurrentScrollPosition = { this.CurrentScrollPosition = {
sH: event.target.scrollHeight, sH: event.target.scrollHeight,
sT: event.target.scrollTop, sT: event.target.scrollTop,
cH: event.target.clientHeight cH: event.target.clientHeight
};
if (
!this.loading &&
this.isScrollDown() &&
this.isScrollExpectPercent() &&
this.currentPage * this.pageSize < this.totalCount
) {
this.loadNextPageEvent.emit();
} }
if (!this.loading
&& this.isScrollDown()
&& this.isScrollExpectPercent()
&& (this.currentPage * this.pageSize < this.totalCount)) {
this.loadNextPageEvent.emit();
}
} }
isScrollDown(): boolean { isScrollDown(): boolean {
@ -113,10 +123,14 @@ export class GridViewComponent implements AfterViewInit {
} }
isScrollExpectPercent(): boolean { isScrollExpectPercent(): boolean {
return ((this.CurrentScrollPosition.sT + this.CurrentScrollPosition.cH) / this.CurrentScrollPosition.sH) > (this.expectScrollPercent / 100); return (
(this.CurrentScrollPosition.sT + this.CurrentScrollPosition.cH) /
this.CurrentScrollPosition.sH >
this.expectScrollPercent / 100
);
} }
@HostListener('window:resize') @HostListener("window:resize")
onResize(event: any) { onResize(event: any) {
this.throttleLayout(); this.throttleLayout();
} }
@ -136,7 +150,7 @@ export class GridViewComponent implements AfterViewInit {
let el = this.itemsHolder.nativeElement; let el = this.itemsHolder.nativeElement;
let width = el.offsetWidth; let width = el.offsetWidth;
let items = el.querySelectorAll('.card-item'); let items = el.querySelectorAll(".card-item");
let items_count = items.length; let items_count = items.length;
if (items_count === 0) { if (items_count === 0) {
el.height = 0; el.height = 0;
@ -158,17 +172,24 @@ export class GridViewComponent implements AfterViewInit {
let maxWidth = parseInt(maxWidthStyle, 10); let maxWidth = parseInt(maxWidthStyle, 10);
let marginHeight: number = let marginHeight: number =
parseInt(itemsStyle.marginTop, 10) + parseInt(itemsStyle.marginBottom, 10); parseInt(itemsStyle.marginTop, 10) +
parseInt(itemsStyle.marginBottom, 10);
let marginWidth: number = let marginWidth: number =
parseInt(itemsStyle.marginLeft, 10) + parseInt(itemsStyle.marginRight, 10); parseInt(itemsStyle.marginLeft, 10) +
parseInt(itemsStyle.marginRight, 10);
let columns = Math.floor(width / (minWidth + marginWidth)); let columns = Math.floor(width / (minWidth + marginWidth));
let columnsToUse = Math.max(Math.min(columns, items_count), 1); let columnsToUse = Math.max(Math.min(columns, items_count), 1);
let rows = Math.floor(items_count / columnsToUse); let rows = Math.floor(items_count / columnsToUse);
let itemWidth = Math.min(Math.floor(width / columnsToUse) - marginWidth, maxWidth); let itemWidth = Math.min(
let itemSpacing = columnsToUse === 1 || columns > items_count ? marginWidth : Math.floor(width / columnsToUse) - marginWidth,
(width - marginWidth - columnsToUse * itemWidth) / (columnsToUse - 1); maxWidth
);
let itemSpacing =
columnsToUse === 1 || columns > items_count
? marginWidth
: (width - marginWidth - columnsToUse * itemWidth) / (columnsToUse - 1);
if (!this.withAdmiral) { if (!this.withAdmiral) {
// Fixed spacing and margin on standalone mode // Fixed spacing and margin on standalone mode
itemSpacing = marginWidth; itemSpacing = marginWidth;
@ -176,7 +197,11 @@ export class GridViewComponent implements AfterViewInit {
} }
let visible = items_count; let visible = items_count;
if (this.hidePartialRows && this.totalItemsCount && items_count !== this.totalItemsCount) { if (
this.hidePartialRows &&
this.totalItemsCount &&
items_count !== this.totalItemsCount
) {
visible = rows * columnsToUse; visible = rows * columnsToUse;
} }
@ -191,27 +216,27 @@ export class GridViewComponent implements AfterViewInit {
// trick to show nice apear animation, where the item is already positioned, // trick to show nice apear animation, where the item is already positioned,
// but it will pop out // but it will pop out
let oldTransform = itemStyle.transform; let oldTransform = itemStyle.transform;
if (!oldTransform || oldTransform === 'none') { if (!oldTransform || oldTransform === "none") {
this.cardStyles[i] = { this.cardStyles[i] = {
transform: 'translate(' + left + 'px,' + top + 'px) scale(0)', transform: "translate(" + left + "px," + top + "px) scale(0)",
width: itemWidth + 'px', width: itemWidth + "px",
transition: 'none', transition: "none",
overflow: 'hidden' overflow: "hidden"
}; };
this.throttleLayout(); this.throttleLayout();
} else { } else {
this.cardStyles[i] = { this.cardStyles[i] = {
transform: 'translate(' + left + 'px,' + top + 'px) scale(1)', transform: "translate(" + left + "px," + top + "px) scale(1)",
width: itemWidth + 'px', width: itemWidth + "px",
transition: null, transition: null,
overflow: 'hidden' overflow: "hidden"
}; };
this.throttleLayout(); this.throttleLayout();
} }
if (!item.classList.contains('context-selected')) { if (!item.classList.contains("context-selected")) {
let itemHeight = itemsHeight[i]; let itemHeight = itemsHeight[i];
if (itemStyle.display === 'none' && itemHeight !== 0) { if (itemStyle.display === "none" && itemHeight !== 0) {
this.cardStyles[i].display = null; this.cardStyles[i].display = null;
} }
if (itemHeight !== 0) { if (itemHeight !== 0) {
@ -222,20 +247,20 @@ export class GridViewComponent implements AfterViewInit {
for (let i = visible; i < items_count; i++) { for (let i = visible; i < items_count; i++) {
this.cardStyles[i] = { this.cardStyles[i] = {
display: 'none' display: "none"
}; };
} }
this.itemsHolderStyle = { this.itemsHolderStyle = {
height: Math.ceil(count / columnsToUse) * (height + marginHeight) + 'px' height: Math.ceil(count / columnsToUse) * (height + marginHeight) + "px"
}; };
} }
onCardEnter(i: number) { onCardEnter(i: number) {
this.cardStyles[i].overflow = 'visible'; this.cardStyles[i].overflow = "visible";
} }
onCardLeave(i: number) { onCardLeave(i: number) {
this.cardStyles[i].overflow = 'hidden'; this.cardStyles[i].overflow = "hidden";
} }
trackByFn(index: number, item: any) { trackByFn(index: number, item: any) {

View File

@ -5,4 +5,4 @@ export * from "./grid-view.component";
export const HBR_GRIDVIEW_DIRECTIVES: Type<any>[] = [ export const HBR_GRIDVIEW_DIRECTIVES: Type<any>[] = [
GridViewComponent GridViewComponent
]; ];

View File

@ -1,4 +1,4 @@
import { NgModule, ModuleWithProviders, Provider, APP_INITIALIZER, Inject } from '@angular/core'; import { NgModule, ModuleWithProviders, Provider, APP_INITIALIZER } from '@angular/core';
import { LOG_DIRECTIVES } from './log/index'; import { LOG_DIRECTIVES } from './log/index';
import { FILTER_DIRECTIVES } from './filter/index'; import { FILTER_DIRECTIVES } from './filter/index';
@ -99,43 +99,43 @@ export const DefaultServiceConfig: IServiceConfig = {
*/ */
export interface HarborModuleConfig { export interface HarborModuleConfig {
// Service endpoints // Service endpoints
config?: Provider, config?: Provider;
// Handling error messages // Handling error messages
errorHandler?: Provider, errorHandler?: Provider;
// Service implementation for system info // Service implementation for system info
systemInfoService?: Provider, systemInfoService?: Provider;
// Service implementation for log // Service implementation for log
logService?: Provider, logService?: Provider;
// Service implementation for endpoint // Service implementation for endpoint
endpointService?: Provider, endpointService?: Provider;
// Service implementation for replication // Service implementation for replication
replicationService?: Provider, replicationService?: Provider;
// Service implementation for repository // Service implementation for repository
repositoryService?: Provider, repositoryService?: Provider;
// Service implementation for tag // Service implementation for tag
tagService?: Provider, tagService?: Provider;
// Service implementation for vulnerability scanning // Service implementation for vulnerability scanning
scanningService?: Provider, scanningService?: Provider;
// Service implementation for configuration // Service implementation for configuration
configService?: Provider, configService?: Provider;
// Service implementation for job log // Service implementation for job log
jobLogService?: Provider, jobLogService?: Provider;
// Service implementation for project policy // Service implementation for project policy
projectPolicyService?: Provider, projectPolicyService?: Provider;
// Service implementation for label // Service implementation for label
labelService?: Provider, labelService?: Provider;
} }
/** /**
@ -263,4 +263,4 @@ export class HarborLibraryModule {
] ]
}; };
} }
} }

View File

@ -1,33 +1,33 @@
export interface i18nConfig { export interface I18nConfig {
/** /**
* The cookie key used to store the current used language preference. * The cookie key used to store the current used language preference.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
langCookieKey?: string, langCookieKey?: string;
/** /**
* Declare what languages are supported. * Declare what languages are supported.
* *
* @type {string[]} * @type {string[]}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
supportedLangs?: string[], supportedLangs?: string[];
/** /**
* Define the default language the translate service uses. * Define the default language the translate service uses.
* *
* @type {string} * @type {string}
* @memberOf i18nConfig * @memberOf I18nConfig
*/ */
defaultLang?: string; defaultLang?: string;
/** /**
* To determine whether or not to enable the i18 multiple languages supporting. * To determine whether or not to enable the i18 multiple languages supporting.
* *
* @type {boolean} * @type {boolean}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
enablei18Support?: boolean; enablei18Support?: boolean;
} }

View File

@ -1,2 +1,2 @@
export * from './translate-init.service'; export * from "./translate-init.service";
export * from './i18n-config'; export * from "./i18n-config";

View File

@ -1,26 +1,28 @@
import { TranslateLoader } from '@ngx-translate/core'; import { TranslateLoader } from "@ngx-translate/core";
import 'rxjs/add/observable/of'; import "rxjs/add/observable/of";
import { Observable } from 'rxjs/Observable'; import { Observable } from "rxjs/Observable";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { IServiceConfig } from "../service.config";
/** /**
* Declare a translation loader with local json object * Declare a translation loader with local json object
* *
* @export * @export
* @class TranslatorJsonLoader * @class TranslatorJsonLoader
* @extends {TranslateLoader} * @extends {TranslateLoader}
*/ */
export class TranslatorJsonLoader extends TranslateLoader { export class TranslatorJsonLoader extends TranslateLoader {
constructor(private config: IServiceConfig) { constructor(private config: IServiceConfig) {
super(); super();
} }
getTranslation(lang: string): Observable<any> { getTranslation(lang: string): Observable<any> {
let dict: any = this.config && let dict: any =
this.config.localI18nMessageVariableMap && this.config &&
this.config.localI18nMessageVariableMap[lang] ? this.config.localI18nMessageVariableMap &&
this.config.localI18nMessageVariableMap[lang] : {}; this.config.localI18nMessageVariableMap[lang]
return Observable.of(dict); ? this.config.localI18nMessageVariableMap[lang]
} : {};
} return Observable.of(dict);
}
}

View File

@ -11,11 +11,14 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core'; import {
MissingTranslationHandler,
MissingTranslationHandlerParams
} from "@ngx-translate/core";
export class MyMissingTranslationHandler implements MissingTranslationHandler { export class MyMissingTranslationHandler implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) { handle(params: MissingTranslationHandlerParams) {
const missingText:string = "{Harbor}"; const missingText: string = "{Harbor}";
return params.key || missingText; return params.key || missingText;
} }
} }

View File

@ -1,40 +1,52 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { i18nConfig } from "./i18n-config"; import { I18nConfig } from "./i18n-config";
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from "@ngx-translate/core";
import { DEFAULT_LANG_COOKIE_KEY, DEFAULT_SUPPORTING_LANGS, DEFAULT_LANG } from '../utils'; import {
import { CookieService } from 'ngx-cookie'; DEFAULT_LANG_COOKIE_KEY,
DEFAULT_SUPPORTING_LANGS,
DEFAULT_LANG
} from "../utils";
import { CookieService } from "ngx-cookie";
@Injectable() @Injectable()
export class TranslateServiceInitializer { export class TranslateServiceInitializer {
constructor( constructor(
private translateService: TranslateService, private translateService: TranslateService,
private cookie: CookieService private cookie: CookieService
) { } ) {}
public init(config: i18nConfig = {}): void { public init(config: I18nConfig = {}): void {
let selectedLang: string = config.defaultLang ? config.defaultLang : DEFAULT_LANG; let selectedLang: string = config.defaultLang
let supportedLangs: string[] = config.supportedLangs ? config.supportedLangs : DEFAULT_SUPPORTING_LANGS; ? config.defaultLang
: DEFAULT_LANG;
let supportedLangs: string[] = config.supportedLangs
? config.supportedLangs
: DEFAULT_SUPPORTING_LANGS;
this.translateService.addLangs(supportedLangs); this.translateService.addLangs(supportedLangs);
this.translateService.setDefaultLang(selectedLang); this.translateService.setDefaultLang(selectedLang);
if (config.enablei18Support) { if (config.enablei18Support) {
//If user has selected lang, then directly use it // If user has selected lang, then directly use it
let langSetting: string = this.cookie.get(config.langCookieKey ? config.langCookieKey : DEFAULT_LANG_COOKIE_KEY); let langSetting: string = this.cookie.get(
if (!langSetting || langSetting.trim() === "") { config.langCookieKey ? config.langCookieKey : DEFAULT_LANG_COOKIE_KEY
//Use browser lang );
langSetting = this.translateService.getBrowserCultureLang().toLowerCase(); if (!langSetting || langSetting.trim() === "") {
} // Use browser lang
langSetting = this.translateService
.getBrowserCultureLang()
.toLowerCase();
}
if (langSetting && langSetting.trim() !== "") { if (langSetting && langSetting.trim() !== "") {
if (supportedLangs && supportedLangs.length > 0) { if (supportedLangs && supportedLangs.length > 0) {
if (supportedLangs.find(lang => lang === langSetting)) { if (supportedLangs.find(lang => lang === langSetting)) {
selectedLang = langSetting; selectedLang = langSetting;
} }
}
}
} }
}
this.translateService.use(selectedLang);
} }
}
this.translateService.use(selectedLang);
}
}

View File

@ -1,25 +1,26 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed, inject } from "@angular/core/testing";
import { SharedModule } from '../shared/shared.module'; import { TranslateService } from "@ngx-translate/core";
import { TranslateService } from '@ngx-translate/core';
import { DEFAULT_LANG } from '../utils';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SharedModule } from "../shared/shared.module";
import { DEFAULT_LANG } from "../utils";
import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
const EN_US_LANG: any = { const EN_US_LANG: any = {
"SIGN_UP": { SIGN_UP: {
"TITLE": "Sign Up" TITLE: "Sign Up"
}, }
} };
const ZH_CN_LANG: any = { const ZH_CN_LANG: any = {
"SIGN_UP": { SIGN_UP: {
"TITLE": "注册" TITLE: "注册"
}, }
} };
describe('TranslateService', () => { describe("TranslateService", () => {
let testConfig: IServiceConfig = { let testConfig: IServiceConfig = {
langMessageLoader: 'local', langMessageLoader: "local",
localI18nMessageVariableMap: { localI18nMessageVariableMap: {
"en-us": EN_US_LANG, "en-us": EN_US_LANG,
"zh-cn": ZH_CN_LANG "zh-cn": ZH_CN_LANG
@ -27,37 +28,49 @@ describe('TranslateService', () => {
}; };
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [SharedModule],
SharedModule providers: [
], {
providers: [{ provide: SERVICE_CONFIG,
provide: SERVICE_CONFIG, useValue: testConfig useValue: testConfig
}] }
]
}); });
}); });
it('should be initialized', inject([TranslateService], (service: TranslateService) => { it(
expect(service).toBeTruthy(); "should be initialized",
})); inject([TranslateService], (service: TranslateService) => {
expect(service).toBeTruthy();
})
);
it('should use the specified lang', inject([TranslateService], (service: TranslateService) => { it(
service.use(DEFAULT_LANG).subscribe(() => { "should use the specified lang",
expect(service.currentLang).toEqual(DEFAULT_LANG); inject([TranslateService], (service: TranslateService) => {
}); service.use(DEFAULT_LANG).subscribe(() => {
})); expect(service.currentLang).toEqual(DEFAULT_LANG);
});
})
);
it('should translate key to text [en-us]', inject([TranslateService], (service: TranslateService) => { it(
service.use(DEFAULT_LANG); "should translate key to text [en-us]",
service.get('SIGN_UP.TITLE').subscribe(text => { inject([TranslateService], (service: TranslateService) => {
expect(text).toEqual('Sign Up'); service.use(DEFAULT_LANG);
}); service.get("SIGN_UP.TITLE").subscribe(text => {
})); expect(text).toEqual("Sign Up");
});
it('should translate key to text [zh-cn]', inject([TranslateService], (service: TranslateService) => { })
service.use('zh-cn'); );
service.get('SIGN_UP.TITLE').subscribe(text => {
expect(text).toEqual('注册');
});
}));
it(
"should translate key to text [zh-cn]",
inject([TranslateService], (service: TranslateService) => {
service.use("zh-cn");
service.get("SIGN_UP.TITLE").subscribe(text => {
expect(text).toEqual("注册");
});
})
);
}); });

View File

@ -4,4 +4,4 @@ import { InlineAlertComponent } from './inline-alert.component';
export const INLINE_ALERT_DIRECTIVES: Type<any>[] = [ export const INLINE_ALERT_DIRECTIVES: Type<any>[] = [
InlineAlertComponent InlineAlertComponent
]; ];

View File

@ -11,86 +11,92 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Component, Output, EventEmitter } from "@angular/core";
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from "@ngx-translate/core";
import { errorHandler } from '../shared/shared.utils'; import { errorHandler } from "../shared/shared.utils";
import { Observable } from 'rxjs/Rx'; // tslint:disable-next-line:no-unused-variable
import { Subscription } from "rxjs"; import { Observable } from "rxjs/Observable";
import { Subscription } from 'rxjs/Subscription';
@Component({ @Component({
selector: 'hbr-inline-alert', selector: "hbr-inline-alert",
templateUrl: './inline-alert.component.html', templateUrl: "./inline-alert.component.html",
styleUrls: [ './inline-alert.component.scss' ] styleUrls: ["./inline-alert.component.scss"]
}) })
export class InlineAlertComponent { export class InlineAlertComponent {
inlineAlertType: string = 'alert-danger'; inlineAlertType: string = "alert-danger";
inlineAlertClosable: boolean = false; inlineAlertClosable: boolean = false;
alertClose: boolean = true; alertClose: boolean = true;
displayedText: string = ""; displayedText: string = "";
showCancelAction: boolean = false; showCancelAction: boolean = false;
useAppLevelStyle: boolean = false; useAppLevelStyle: boolean = false;
timer: Subscription | null = null; timer: Subscription | null = null;
count: number = 0; count: number = 0;
blinking: boolean = false; blinking: boolean = false;
@Output() confirmEvt = new EventEmitter<boolean>(); @Output() confirmEvt = new EventEmitter<boolean>();
constructor(private translate: TranslateService) { } constructor(private translate: TranslateService) {}
public get errorMessage(): string { public get errorMessage(): string {
return this.displayedText; return this.displayedText;
}
// Show error message inline
public showInlineError(error: any): void {
this.displayedText = errorHandler(error);
if (this.displayedText) {
this.translate
.get(this.displayedText)
.subscribe((res: string) => (this.displayedText = res));
} }
//Show error message inline this.inlineAlertType = "alert-danger";
public showInlineError(error: any): void { this.showCancelAction = false;
this.displayedText = errorHandler(error); this.inlineAlertClosable = true;
if (this.displayedText) { this.alertClose = false;
this.translate.get(this.displayedText).subscribe((res: string) => this.displayedText = res); this.useAppLevelStyle = false;
} }
this.inlineAlertType = 'alert-danger'; // Show confirmation info with action button
this.showCancelAction = false; public showInlineConfirmation(warning: any): void {
this.inlineAlertClosable = true; this.displayedText = "";
this.alertClose = false; if (warning && warning.message) {
this.useAppLevelStyle = false; this.translate
.get(warning.message)
.subscribe((res: string) => (this.displayedText = res));
} }
this.inlineAlertType = "alert-warning";
this.showCancelAction = true;
this.inlineAlertClosable = false;
this.alertClose = false;
this.useAppLevelStyle = false;
}
//Show confirmation info with action button // Show inline sccess info
public showInlineConfirmation(warning: any): void { public showInlineSuccess(info: any): void {
this.displayedText = ""; this.displayedText = "";
if (warning && warning.message) { if (info && info.message) {
this.translate.get(warning.message).subscribe((res: string) => this.displayedText = res); this.translate
} .get(info.message)
this.inlineAlertType = 'alert-warning'; .subscribe((res: string) => (this.displayedText = res));
this.showCancelAction = true;
this.inlineAlertClosable = false;
this.alertClose = false;
this.useAppLevelStyle = false;
} }
this.inlineAlertType = "alert-success";
this.showCancelAction = false;
this.inlineAlertClosable = true;
this.alertClose = false;
this.useAppLevelStyle = false;
}
//Show inline sccess info // Close alert
public showInlineSuccess(info: any): void { public close(): void {
this.displayedText = ""; this.alertClose = true;
if (info && info.message) { }
this.translate.get(info.message).subscribe((res: string) => this.displayedText = res);
}
this.inlineAlertType = 'alert-success';
this.showCancelAction = false;
this.inlineAlertClosable = true;
this.alertClose = false;
this.useAppLevelStyle = false;
}
//Close alert public blink() {}
public close(): void {
this.alertClose = true;
}
public blink() { confirmCancel(): void {
} this.confirmEvt.emit(true);
}
confirmCancel(): void { }
this.confirmEvt.emit(true);
}
}

View File

@ -6,4 +6,4 @@ export * from './job-log-viewer.component';
export const JOB_LOG_VIEWER_DIRECTIVES: Type<any>[] = [ export const JOB_LOG_VIEWER_DIRECTIVES: Type<any>[] = [
JobLogViewerComponent JobLogViewerComponent
]; ];

View File

@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DebugElement } from '@angular/core'; // tslint:disable-next-line:no-unused-variable
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { JobLogService, JobLogDefaultService } from '../service/index'; import { JobLogService, JobLogDefaultService } from '../service/index';

View File

@ -11,76 +11,80 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from '@angular/core'; import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input
} from "@angular/core";
import { JobLogService } from '../service/index'; import { JobLogService } from "../service/index";
import { ErrorHandler } from '../error-handler/index'; import { ErrorHandler } from "../error-handler/index";
import { toPromise } from '../utils'; import { toPromise } from "../utils";
const supportSet: string[] = ["replication", "scan"]; const supportSet: string[] = ["replication", "scan"];
@Component({ @Component({
selector: 'job-log-viewer', selector: "job-log-viewer",
templateUrl: './job-log-viewer.component.html', templateUrl: "./job-log-viewer.component.html",
styleUrls: ['./job-log-viewer.component.scss'], styleUrls: ["./job-log-viewer.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class JobLogViewerComponent { export class JobLogViewerComponent {
_jobType: string = "replication"; _jobType: string = "replication";
opened: boolean = false; opened: boolean = false;
log: string = ''; log: string = "";
onGoing: boolean = true; onGoing: boolean = true;
@Input() @Input()
get jobType(): string { get jobType(): string {
return this._jobType; return this._jobType;
}
set jobType(v: string) {
if (supportSet.find((t: string) => t === v)) {
this._jobType = v;
} }
set jobType(v: string) { }
if (supportSet.find((t: string) => t === v)) {
this._jobType = v; get title(): string {
} if (this.jobType === "scan") {
return "VULNERABILITY.JOB_LOG_VIEWER";
} }
get title(): string { return "REPLICATION.JOB_LOG_VIEWER";
if(this.jobType === "scan"){ }
return "VULNERABILITY.JOB_LOG_VIEWER";
}
return "REPLICATION.JOB_LOG_VIEWER"; constructor(
} private jobLogService: JobLogService,
private errorHandler: ErrorHandler,
private ref: ChangeDetectorRef
) {}
constructor( open(jobId: number | string): void {
private jobLogService: JobLogService, this.opened = true;
private errorHandler: ErrorHandler, this.load(jobId);
private ref: ChangeDetectorRef }
) { }
open(jobId: number | string): void { close(): void {
this.opened = true; this.opened = false;
this.load(jobId); this.log = "";
} }
close(): void { load(jobId: number | string): void {
this.opened = false; this.onGoing = true;
this.log = "";
}
load(jobId: number | string): void { toPromise<string>(this.jobLogService.getJobLog(this.jobType, jobId))
this.onGoing = true; .then((log: string) => {
this.onGoing = false;
this.log = log;
})
.catch(error => {
this.onGoing = false;
this.errorHandler.error(error);
});
toPromise<string>(this.jobLogService.getJobLog(this.jobType, jobId)) let hnd = setInterval(() => this.ref.markForCheck(), 100);
.then((log: string) => { setTimeout(() => clearInterval(hnd), 2000);
this.onGoing = false; }
this.log = log; }
})
.catch(error => {
this.onGoing = false;
this.errorHandler.error(error);
});
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
setTimeout(()=>clearInterval(hnd), 2000);
}
}

View File

@ -5,4 +5,4 @@ import {LabelPieceComponent} from './label-piece.component';
export const LABEL_PIECE_DIRECTIVES: Type<any>[] = [ export const LABEL_PIECE_DIRECTIVES: Type<any>[] = [
LabelPieceComponent LabelPieceComponent
]; ];

View File

@ -11,9 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Component, Input, Output, OnInit, EventEmitter, OnChanges} from '@angular/core'; import {Component, Input, OnInit, OnChanges} from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/distinctUntilChanged';
@ -41,4 +39,4 @@ export class LabelPieceComponent implements OnInit, OnChanges {
} }
ngOnInit(): void { } ngOnInit(): void { }
} }

View File

@ -3,4 +3,4 @@ import {LabelComponent} from "./label.component";
export const LABEL_DIRECTIVES: Type<any>[] = [ export const LABEL_DIRECTIVES: Type<any>[] = [
LabelComponent LabelComponent
]; ];

View File

@ -3,7 +3,7 @@
<div> <div>
<div class="row flex-items-xs-between rightPos"> <div class="row flex-items-xs-between rightPos">
<div class="flex-items-xs-middle option-right"> <div class="flex-items-xs-middle option-right">
<hbr-filter [withDivider]="true" filterPlaceholder='{{"LABEL.FILTER_LABEL_PLACEHOLDER" | translate}}' (filter)="doSearchTargets($event)" [currentValue]="targetName"></hbr-filter> <hbr-filter [withDivider]="true" filterPlaceholder='{{"LABEL.FILTER_LABEL_PLACEHOLDER" | translate}}' (filterEvt)="doSearchTargets($event)" [currentValue]="targetName"></hbr-filter>
<span class="refresh-btn" (click)="refreshTargets()"> <span class="refresh-btn" (click)="refreshTargets()">
<clr-icon shape="refresh"></clr-icon> <clr-icon shape="refresh"></clr-icon>
</span> </span>

View File

@ -48,7 +48,7 @@ describe('LabelComponent (inline template)', () => {
project_id: 0, project_id: 0,
scope: "g", scope: "g",
update_time: "", update_time: "",
} };
let comp: LabelComponent; let comp: LabelComponent;
let fixture: ComponentFixture<LabelComponent>; let fixture: ComponentFixture<LabelComponent>;
@ -107,7 +107,7 @@ describe('LabelComponent (inline template)', () => {
comp.editLabel([mockOneData]); comp.editLabel([mockOneData]);
fixture.detectChanges(); fixture.detectChanges();
expect(comp.targets[0].name).toEqual('label0-g'); expect(comp.targets[0].name).toEqual('label0-g');
}) });
})); }));
/*it('should open to edit existing label', async() => { /*it('should open to edit existing label', async() => {
@ -128,4 +128,4 @@ describe('LabelComponent (inline template)', () => {
}) })
})*/ })*/
}) });

View File

@ -12,162 +12,174 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { import {
Component, OnInit, OnDestroy, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, Component,
Input OnInit,
} from '@angular/core'; ViewChild,
import {Label} from "../service/interface"; ChangeDetectionStrategy,
import {LabelDefaultService, LabelService} from "../service/label.service"; ChangeDetectorRef,
import {toPromise} from "../utils"; Input
import {ErrorHandler} from "../error-handler/error-handler"; } from "@angular/core";
import {CreateEditLabelComponent} from "../create-edit-label/create-edit-label.component"; import { Label } from "../service/interface";
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message"; import { LabelService } from "../service/label.service";
import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message"; import { toPromise } from "../utils";
import {ConfirmationButtons, ConfirmationState, ConfirmationTargets} from "../shared/shared.const"; import { ErrorHandler } from "../error-handler/error-handler";
import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message"; import { CreateEditLabelComponent } from "../create-edit-label/create-edit-label.component";
import {TranslateService} from "@ngx-translate/core"; import {
import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component"; BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import {
ConfirmationButtons,
ConfirmationState,
ConfirmationTargets
} from "../shared/shared.const";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
import { TranslateService } from "@ngx-translate/core";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
@Component({ @Component({
selector: 'hbr-label', selector: "hbr-label",
templateUrl: './label.component.html', templateUrl: "./label.component.html",
styleUrls: ['./label.component.scss'], styleUrls: ["./label.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class LabelComponent implements OnInit { export class LabelComponent implements OnInit {
timerHandler: any; timerHandler: any;
loading: boolean; loading: boolean;
targets: Label[]; targets: Label[];
targetName: string; targetName: string;
selectedRow: Label[] = []; selectedRow: Label[] = [];
batchDelectionInfos: BatchInfo[] = []; batchDelectionInfos: BatchInfo[] = [];
@Input() scope: string; @Input() scope: string;
@Input() projectId = 0; @Input() projectId = 0;
@Input() hasProjectAdminRole: boolean; @Input() hasProjectAdminRole: boolean;
@ViewChild(CreateEditLabelComponent) @ViewChild(CreateEditLabelComponent)
createEditLabel: CreateEditLabelComponent; createEditLabel: CreateEditLabelComponent;
@ViewChild('confirmationDialog') @ViewChild("confirmationDialog")
confirmationDialogComponent: ConfirmationDialogComponent; confirmationDialogComponent: ConfirmationDialogComponent;
constructor( constructor(
private labelService: LabelService, private labelService: LabelService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private translateService: TranslateService, private translateService: TranslateService,
private ref: ChangeDetectorRef) { private ref: ChangeDetectorRef
) {}
ngOnInit(): void {
this.retrieve(this.scope);
}
retrieve(scope: string, name = "") {
this.loading = true;
this.selectedRow = [];
this.targetName = "";
toPromise<Label[]>(this.labelService.getLabels(scope, this.projectId, name))
.then(targets => {
this.targets = targets || [];
this.loading = false;
this.forceRefreshView(2000);
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
openModal(): void {
this.createEditLabel.openModal();
}
reload(): void {
this.retrieve(this.scope);
}
doSearchTargets(targetName: string) {
this.retrieve(this.scope, targetName);
}
refreshTargets() {
this.retrieve(this.scope);
}
selectedChange(): void {
// this.forceRefreshView(5000);
}
editLabel(label: Label[]): void {
this.createEditLabel.editModel(label[0].id, label);
}
deleteLabels(targets: Label[]): void {
if (targets && targets.length) {
let targetNames: string[] = [];
this.batchDelectionInfos = [];
targets.forEach(target => {
targetNames.push(target.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = target.name;
this.batchDelectionInfos.push(initBatchMessage);
});
let deletionMessage = new ConfirmationMessage(
"LABEL.DELETION_TITLE_TARGET",
"LABEL.DELETION_SUMMARY_TARGET",
targetNames.join(", ") || "",
targets,
ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL
);
this.confirmationDialogComponent.open(deletionMessage);
} }
}
ngOnInit(): void { confirmDeletion(message: ConfirmationAcknowledgement) {
this.retrieve(this.scope); if (
message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED
) {
let targetLists: Label[] = message.data;
if (targetLists && targetLists.length) {
let promiseLists: any[] = [];
targetLists.forEach(target => {
promiseLists.push(this.delOperate(target.id, target.name));
});
Promise.all(promiseLists).then(item => {
this.selectedRow = [];
this.retrieve(this.scope);
});
}
} }
}
retrieve(scope: string, name = '') { delOperate(id: number, name: string) {
this.loading = true; let findedList = this.batchDelectionInfos.find(data => data.name === name);
this.selectedRow = []; return toPromise<number>(this.labelService.deleteLabel(id))
this.targetName = ''; .then(response => {
toPromise<Label[]>(this.labelService.getLabels(scope, this.projectId, name)) this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => {
.then(targets => { findedList = BathInfoChanges(findedList, res);
this.targets = targets || []; });
this.loading = false; })
this.forceRefreshView(2000); .catch(error => {
}).catch(error => { this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
this.errorHandler.error(error); findedList = BathInfoChanges(findedList, res, false, true);
this.loading = false; });
}) });
} }
openModal(): void { // Forcely refresh the view
this.createEditLabel.openModal(); forceRefreshView(duration: number): void {
// Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
} }
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
reload(): void { setTimeout(() => {
this.retrieve(this.scope); if (this.timerHandler) {
} clearInterval(this.timerHandler);
this.timerHandler = null;
doSearchTargets(targetName: string) { }
this.retrieve(this.scope, targetName); }, duration);
} }
}
refreshTargets() {
this.retrieve(this.scope);
}
selectedChange(): void {
// this.forceRefreshView(5000);
}
editLabel(label: Label[]): void {
this.createEditLabel.editModel(label[0].id, label);
}
deleteLabels(targets: Label[]): void {
if (targets && targets.length) {
let targetNames: string[] = [];
this.batchDelectionInfos = [];
targets.forEach(target => {
targetNames.push(target.name);
let initBatchMessage = new BatchInfo ();
initBatchMessage.name = target.name;
this.batchDelectionInfos.push(initBatchMessage);
});
let deletionMessage = new ConfirmationMessage(
'LABEL.DELETION_TITLE_TARGET',
'LABEL.DELETION_SUMMARY_TARGET',
targetNames.join(', ') || '',
targets,
ConfirmationTargets.TARGET,
ConfirmationButtons.DELETE_CANCEL);
this.confirmationDialogComponent.open(deletionMessage);
}
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED) {
let targetLists: Label[] = message.data;
if (targetLists && targetLists.length) {
let promiseLists: any[] = [];
targetLists.forEach(target => {
promiseLists.push(this.delOperate(target.id, target.name));
})
Promise.all(promiseLists).then((item) => {
this.selectedRow = [];
this.retrieve(this.scope);
});
}
}
}
delOperate(id: number, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<number>(this.labelService
.deleteLabel(id))
.then(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => {
findedList = BathInfoChanges(findedList, res);
});
}).catch(
error => {
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
});
}
// Forcely refresh the view
forceRefreshView(duration: number): void {
// Reset timer
if (this.timerHandler) {
clearInterval(this.timerHandler);
}
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => {
if (this.timerHandler) {
clearInterval(this.timerHandler);
this.timerHandler = null;
}
}, duration);
}
}

View File

@ -6,4 +6,4 @@ export * from './list-replication-rule.component';
export const LIST_REPLICATION_RULE_DIRECTIVES: Type<any>[] = [ export const LIST_REPLICATION_RULE_DIRECTIVES: Type<any>[] = [
ListReplicationRuleComponent ListReplicationRuleComponent
]; ];

View File

@ -1,4 +1,4 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { NoopAnimationsModule } from "@angular/platform-browser/animations";
@ -14,7 +14,7 @@ import { ErrorHandler } from '../error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service'; import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
describe('ListReplicationRuleComponent (inline template)', ()=>{ describe('ListReplicationRuleComponent (inline template)', () => {
let mockRules: ReplicationRule[] = [ let mockRules: ReplicationRule[] = [
{ {
@ -100,20 +100,20 @@ describe('ListReplicationRuleComponent (inline template)', ()=>{
]; ];
let fixture: ComponentFixture<ListReplicationRuleComponent>; let fixture: ComponentFixture<ListReplicationRuleComponent>;
let comp: ListReplicationRuleComponent; let comp: ListReplicationRuleComponent;
let replicationService: ReplicationService; let replicationService: ReplicationService;
let spyRules: jasmine.Spy; let spyRules: jasmine.Spy;
let config: IServiceConfig = { let config: IServiceConfig = {
replicationRuleEndpoint: '/api/policies/replication/testing' replicationRuleEndpoint: '/api/policies/replication/testing'
}; };
beforeEach(async(()=>{ beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
SharedModule, SharedModule,
NoopAnimationsModule NoopAnimationsModule
], ],
@ -129,7 +129,7 @@ describe('ListReplicationRuleComponent (inline template)', ()=>{
}); });
})); }));
beforeEach(()=>{ beforeEach(() => {
fixture = TestBed.createComponent(ListReplicationRuleComponent); fixture = TestBed.createComponent(ListReplicationRuleComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
replicationService = fixture.debugElement.injector.get(ReplicationService); replicationService = fixture.debugElement.injector.get(ReplicationService);
@ -137,9 +137,9 @@ describe('ListReplicationRuleComponent (inline template)', ()=>{
fixture.detectChanges(); fixture.detectChanges();
}); });
it('Should load and render data', async(()=>{ it('Should load and render data', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(By.css('datagrid-cell')); let de: DebugElement = fixture.debugElement.query(By.css('datagrid-cell'));
expect(de).toBeTruthy(); expect(de).toBeTruthy();
@ -149,4 +149,4 @@ describe('ListReplicationRuleComponent (inline template)', ()=>{
}); });
})); }));
}); });

View File

@ -23,36 +23,40 @@ import {
OnChanges, OnChanges,
SimpleChange, SimpleChange,
SimpleChanges SimpleChanges
} from '@angular/core'; } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { Comparator } from "clarity-angular";
import { TranslateService } from "@ngx-translate/core";
import { ReplicationService } from '../service/replication.service'; import { ReplicationService } from "../service/replication.service";
import {ReplicationJob, ReplicationJobItem, ReplicationRule} from '../service/interface'; import {
ReplicationJob,
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; ReplicationJobItem,
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message'; ReplicationRule
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message'; } from "../service/interface";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const'; import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
import { TranslateService } from '@ngx-translate/core'; import {
ConfirmationState,
import { ErrorHandler } from '../error-handler/error-handler'; ConfirmationTargets,
import { toPromise, CustomComparator } from '../utils'; ConfirmationButtons
} from "../shared/shared.const";
import { State, Comparator } from 'clarity-angular'; import { ErrorHandler } from "../error-handler/error-handler";
import { toPromise, CustomComparator } from "../utils";
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message"; import {
import {Observable} from "rxjs/Observable"; BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
@Component({ @Component({
selector: 'hbr-list-replication-rule', selector: "hbr-list-replication-rule",
templateUrl: './list-replication-rule.component.html', templateUrl: "./list-replication-rule.component.html",
styleUrls: ['./list-replication-rule.component.scss'], styleUrls: ["./list-replication-rule.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ListReplicationRuleComponent implements OnInit, OnChanges { export class ListReplicationRuleComponent implements OnInit, OnChanges {
nullTime = "0001-01-01T00:00:00Z";
nullTime = '0001-01-01T00:00:00Z';
@Input() projectId: number; @Input() projectId: number;
@Input() isSystemAdmin: boolean; @Input() isSystemAdmin: boolean;
@ -80,25 +84,30 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
selectedRow: ReplicationRule; selectedRow: ReplicationRule;
batchDelectionInfos: BatchInfo[] = []; batchDelectionInfos: BatchInfo[] = [];
@ViewChild('toggleConfirmDialog') @ViewChild("toggleConfirmDialog")
toggleConfirmDialog: ConfirmationDialogComponent; toggleConfirmDialog: ConfirmationDialogComponent;
@ViewChild('deletionConfirmDialog') @ViewChild("deletionConfirmDialog")
deletionConfirmDialog: ConfirmationDialogComponent; deletionConfirmDialog: ConfirmationDialogComponent;
startTimeComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>('start_time', 'date'); startTimeComparator: Comparator<ReplicationRule> = new CustomComparator<
enabledComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>('enabled', 'number'); ReplicationRule
>("start_time", "date");
enabledComparator: Comparator<ReplicationRule> = new CustomComparator<
ReplicationRule
>("enabled", "number");
constructor( constructor(
private replicationService: ReplicationService, private replicationService: ReplicationService,
private translateService: TranslateService, private translateService: TranslateService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private ref: ChangeDetectorRef) { private ref: ChangeDetectorRef
) {
setInterval(() => ref.markForCheck(), 500); setInterval(() => ref.markForCheck(), 500);
} }
trancatedDescription(desc: string): string { trancatedDescription(desc: string): string {
if (desc.length > 35 ) { if (desc.length > 35) {
return desc.substr(0, 35); return desc.substr(0, 35);
} else { } else {
return desc; return desc;
@ -126,19 +135,20 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
} }
} }
retrieveRules(ruleName = ''): void { retrieveRules(ruleName = ""): void {
this.loading = true; this.loading = true;
/*this.selectedRow = null;*/ /*this.selectedRow = null;*/
toPromise<ReplicationRule[]>(this.replicationService toPromise<ReplicationRule[]>(
.getReplicationRules(this.projectId, ruleName)) this.replicationService.getReplicationRules(this.projectId, ruleName)
)
.then(rules => { .then(rules => {
this.rules = rules || []; this.rules = rules || [];
// job list hidden // job list hidden
this.hideJobs.emit(); this.hideJobs.emit();
this.changedRules = this.rules; this.changedRules = this.rules;
this.loading = false; this.loading = false;
} })
).catch(error => { .catch(error => {
this.errorHandler.error(error); this.errorHandler.error(error);
this.loading = false; this.loading = false;
}); });
@ -149,16 +159,17 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
} }
deletionConfirm(message: ConfirmationAcknowledgement) { deletionConfirm(message: ConfirmationAcknowledgement) {
if (message && if (
message &&
message.source === ConfirmationTargets.POLICY && message.source === ConfirmationTargets.POLICY &&
message.state === ConfirmationState.CONFIRMED) { message.state === ConfirmationState.CONFIRMED
) {
this.deleteOpe(message.data); this.deleteOpe(message.data);
} }
} }
selectRule(rule: ReplicationRule): void { selectRule(rule: ReplicationRule): void {
this.selectedId = rule.id || ''; this.selectedId = rule.id || "";
this.selectOne.emit(rule); this.selectOne.emit(rule);
} }
@ -178,20 +189,23 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
let ruleData: ReplicationJobItem[]; let ruleData: ReplicationJobItem[];
this.canDeleteRule = true; this.canDeleteRule = true;
let count = 0; let count = 0;
return toPromise<ReplicationJob>(this.replicationService return toPromise<ReplicationJob>(this.replicationService.getJobs(id))
.getJobs(id)) .then(response => {
.then(response => { ruleData = response.data;
ruleData = response.data; if (ruleData.length) {
if (ruleData.length) { ruleData.forEach(job => {
ruleData.forEach(job => { if (
if ((job.status === 'pending') || (job.status === 'running') || (job.status === 'retrying')) { job.status === "pending" ||
count ++; job.status === "running" ||
} job.status === "retrying"
}); ) {
} count++;
this.canDeleteRule = count > 0 ? false : true; }
}) });
.catch(error => this.errorHandler.error(error)); }
this.canDeleteRule = count > 0 ? false : true;
})
.catch(error => this.errorHandler.error(error));
} }
deleteRule(rule: ReplicationRule) { deleteRule(rule: ReplicationRule) {
@ -201,12 +215,13 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
initBatchMessage.name = rule.name; initBatchMessage.name = rule.name;
this.batchDelectionInfos.push(initBatchMessage); this.batchDelectionInfos.push(initBatchMessage);
let deletionMessage = new ConfirmationMessage( let deletionMessage = new ConfirmationMessage(
'REPLICATION.DELETION_TITLE', "REPLICATION.DELETION_TITLE",
'REPLICATION.DELETION_SUMMARY', "REPLICATION.DELETION_SUMMARY",
rule.name, rule.name,
rule, rule,
ConfirmationTargets.POLICY, ConfirmationTargets.POLICY,
ConfirmationButtons.DELETE_CANCEL); ConfirmationButtons.DELETE_CANCEL
);
this.deletionConfirmDialog.open(deletionMessage); this.deletionConfirmDialog.open(deletionMessage);
} }
} }
@ -215,10 +230,20 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
let promiseLists: any[] = []; let promiseLists: any[] = [];
Promise.all([this.jobList(rule.id)]).then(items => { Promise.all([this.jobList(rule.id)]).then(items => {
if (!this.canDeleteRule) { if (!this.canDeleteRule) {
let findedList = this.batchDelectionInfos.find(data => data.name === rule.name); let findedList = this.batchDelectionInfos.find(
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), data => data.name === rule.name
this.translateService.get('REPLICATION.DELETION_SUMMARY_FAILURE')).subscribe(res => { );
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]); Observable.forkJoin(
this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get("REPLICATION.DELETION_SUMMARY_FAILURE")
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
}); });
} else { } else {
promiseLists.push(this.delOperate(+rule.id, rule.name)); promiseLists.push(this.delOperate(+rule.id, rule.name));
@ -234,25 +259,35 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
} }
} }
delOperate(ruleId: number, name: string) { delOperate(ruleId: number, name: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === name); let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<any>(this.replicationService return toPromise<any>(this.replicationService.deleteReplicationRule(ruleId))
.deleteReplicationRule(ruleId)) .then(() => {
.then(() => { this.translateService
this.translateService.get('BATCH.DELETED_SUCCESS') .get("BATCH.DELETED_SUCCESS")
.subscribe(res => findedList = BathInfoChanges(findedList, res)); .subscribe(res => (findedList = BathInfoChanges(findedList, res)));
}) })
.catch(error => { .catch(error => {
if (error && error.status === 412) { if (error && error.status === 412) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), Observable.forkJoin(
this.translateService.get('REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED')).subscribe(res => { this.translateService.get("BATCH.DELETED_FAILURE"),
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]); this.translateService.get(
}); "REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED"
} else { )
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { ).subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); findedList = BathInfoChanges(
}); findedList,
} res[0],
false,
true,
res[1]
);
}); });
} } else {
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
}
});
}
} }

View File

@ -5,4 +5,4 @@ export * from "./recent-log.component";
export const LOG_DIRECTIVES: Type<any>[] = [ export const LOG_DIRECTIVES: Type<any>[] = [
RecentLogComponent RecentLogComponent
]; ];

View File

@ -11,7 +11,7 @@
<option value="operation">{{"AUDIT_LOG.OPERATION" | translate | lowercase}}</option> <option value="operation">{{"AUDIT_LOG.OPERATION" | translate | lowercase}}</option>
</select> </select>
</div> </div>
<hbr-filter [withDivider]="true" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)" <hbr-filter [withDivider]="true" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filterEvt)="doFilter($event)"
(openFlag)="openFilter($event)" [currentValue]="currentTerm"></hbr-filter> (openFlag)="openFilter($event)" [currentValue]="currentTerm"></hbr-filter>
<span (click)="refresh()" class="refresh-btn"> <span (click)="refresh()" class="refresh-btn">
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon> <clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>

View File

@ -1,8 +1,5 @@
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; 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 { DebugElement } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { AccessLog, AccessLogItem, RequestQueryParams } from '../service/index'; import { AccessLog, AccessLogItem, RequestQueryParams } from '../service/index';
import { RecentLogComponent } from './recent-log.component'; import { RecentLogComponent } from './recent-log.component';
@ -83,7 +80,7 @@ describe('RecentLogComponent (inline template)', () => {
if (params.get('page') === '1') { if (params.get('page') === '1') {
mockData.data = mockItems.slice(0, 15); mockData.data = mockItems.slice(0, 15);
} else { } else {
mockData.data = mockItems.slice(15, 18) mockData.data = mockItems.slice(15, 18);
} }
return Promise.resolve(mockData); return Promise.resolve(mockData);
} }
@ -194,7 +191,7 @@ describe('RecentLogComponent (inline template)', () => {
let els: HTMLElement[] = fixture.nativeElement.querySelectorAll('.datagrid-row'); let els: HTMLElement[] = fixture.nativeElement.querySelectorAll('.datagrid-row');
expect(els).toBeTruthy(); expect(els).toBeTruthy();
expect(els.length).toEqual(4) expect(els.length).toEqual(4);
let refreshEl: HTMLElement = fixture.nativeElement.querySelector(".refresh-btn"); let refreshEl: HTMLElement = fixture.nativeElement.querySelector(".refresh-btn");
expect(refreshEl).toBeTruthy("Not found refresh button"); expect(refreshEl).toBeTruthy("Not found refresh button");

View File

@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { Router } from '@angular/router'; import { Comparator, State } from 'clarity-angular';
import { import {
AccessLogService, AccessLogService,
AccessLog, AccessLog,
@ -20,7 +21,6 @@ import {
RequestQueryParams RequestQueryParams
} from '../service/index'; } from '../service/index';
import { ErrorHandler } from '../error-handler/index'; import { ErrorHandler } from '../error-handler/index';
import { Observable } from 'rxjs/Observable';
import { toPromise, CustomComparator } from '../utils'; import { toPromise, CustomComparator } from '../utils';
import { import {
DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE,
@ -29,8 +29,6 @@ import {
doSorting doSorting
} from '../utils'; } from '../utils';
import { Comparator, State } from 'clarity-angular';
@Component({ @Component({
selector: 'hbr-log', selector: 'hbr-log',
templateUrl: './recent-log.component.html', templateUrl: './recent-log.component.html',
@ -47,8 +45,8 @@ export class RecentLogComponent implements OnInit {
@Input() withTitle: boolean = false; @Input() withTitle: boolean = false;
pageSize: number = DEFAULT_PAGE_SIZE; pageSize: number = DEFAULT_PAGE_SIZE;
currentPage: number = 1;//Double bound to pagination component currentPage: number = 1; // Double bound to pagination component
currentPagePvt: number = 0; //Used to confirm whether page is changed currentPagePvt: number = 0; // Used to confirm whether page is changed
currentState: State; currentState: State;
opTimeComparator: Comparator<AccessLogItem> = new CustomComparator<AccessLogItem>('op_time', 'date'); opTimeComparator: Comparator<AccessLogItem> = new CustomComparator<AccessLogItem>('op_time', 'date');
@ -70,11 +68,11 @@ export class RecentLogComponent implements OnInit {
public doFilter(terms: string): void { public doFilter(terms: string): void {
this.currentTerm = terms.trim(); this.currentTerm = terms.trim();
//Trigger data loading and start from first page // Trigger data loading and start from first page
this.loading = true; this.loading = true;
this.currentPage = 1; this.currentPage = 1;
if (this.currentPagePvt === 1) { if (this.currentPagePvt === 1) {
//Force reloading // Force reloading
let st: State = this.currentState; let st: State = this.currentState;
if (!st) { if (!st) {
st = { st = {
@ -85,7 +83,7 @@ export class RecentLogComponent implements OnInit {
st.page.to = this.pageSize - 1; st.page.to = this.pageSize - 1;
st.page.size = this.pageSize; st.page.size = this.pageSize;
this.currentPagePvt = 0;//Reset pvt this.currentPagePvt = 0; // Reset pvt
this.load(st); this.load(st);
} }
@ -109,12 +107,12 @@ export class RecentLogComponent implements OnInit {
} }
load(state: State) { load(state: State) {
//Keep it for future filter // Keep it for future filter
this.currentState = state; this.currentState = state;
let pageNumber: number = calculatePage(state); let pageNumber: number = calculatePage(state);
if (pageNumber !== this.currentPagePvt) { if (pageNumber !== this.currentPagePvt) {
//load data // load data
let params: RequestQueryParams = new RequestQueryParams(); let params: RequestQueryParams = new RequestQueryParams();
params.set("page", '' + pageNumber); params.set("page", '' + pageNumber);
params.set("page_size", '' + this.pageSize); params.set("page_size", '' + this.pageSize);
@ -125,13 +123,13 @@ export class RecentLogComponent implements OnInit {
this.loading = true; this.loading = true;
toPromise<AccessLog>(this.logService.getRecentLogs(params)) toPromise<AccessLog>(this.logService.getRecentLogs(params))
.then(response => { .then(response => {
this.logsCache = response; //Keep the data this.logsCache = response; // Keep the data
this.recentLogs = this.logsCache.data.filter(log => log.username != "");//To display this.recentLogs = this.logsCache.data.filter(log => log.username !== ""); // To display
//Do customized filter // Do customized filter
this.recentLogs = doFiltering<AccessLogItem>(this.recentLogs, state); this.recentLogs = doFiltering<AccessLogItem>(this.recentLogs, state);
//Do customized sorting // Do customized sorting
this.recentLogs = doSorting<AccessLogItem>(this.recentLogs, state); this.recentLogs = doSorting<AccessLogItem>(this.recentLogs, state);
this.currentPagePvt = pageNumber; this.currentPagePvt = pageNumber;
@ -143,14 +141,14 @@ export class RecentLogComponent implements OnInit {
this.errorHandler.error(error); this.errorHandler.error(error);
}); });
} else { } else {
//Column sorting and filtering // Column sorting and filtering
this.recentLogs = this.logsCache.data.filter(log => log.username != "");//Reset data this.recentLogs = this.logsCache.data.filter(log => log.username !== ""); // Reset data
//Do customized filter // Do customized filter
this.recentLogs = doFiltering<AccessLogItem>(this.recentLogs, state); this.recentLogs = doFiltering<AccessLogItem>(this.recentLogs, state);
//Do customized sorting // Do customized sorting
this.recentLogs = doSorting<AccessLogItem>(this.recentLogs, state); this.recentLogs = doSorting<AccessLogItem>(this.recentLogs, state);
} }
} }
@ -162,4 +160,4 @@ export class RecentLogComponent implements OnInit {
reg.test(log.operation) || reg.test(log.operation) ||
reg.test(log.repo_tag); reg.test(log.repo_tag);
} }
} }

View File

@ -5,7 +5,7 @@ import { ProjectService } from '../service/project.service';
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from '../error-handler/error-handler';
import { State } from 'clarity-angular'; import { State } from 'clarity-angular';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const'; import { ConfirmationState, ConfirmationTargets } from '../shared/shared.const';
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message'; import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message'; import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';

View File

@ -8,4 +8,4 @@ export * from './copy-input.component';
export const PUSH_IMAGE_BUTTON_DIRECTIVES: Type<any>[] = [ export const PUSH_IMAGE_BUTTON_DIRECTIVES: Type<any>[] = [
CopyInputComponent, CopyInputComponent,
PushImageButtonComponent PushImageButtonComponent
]; ];

View File

@ -1,8 +1,4 @@
import { async, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; import { async, ComponentFixture, TestBed, fakeAsync } 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 { PushImageButtonComponent } from './push-image.component';
import { CopyInputComponent } from './copy-input.component'; import { CopyInputComponent } from './copy-input.component';
@ -10,7 +6,6 @@ import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { click } from '../utils';
describe('PushImageButtonComponent (inline template)', () => { describe('PushImageButtonComponent (inline template)', () => {
let component: PushImageButtonComponent; let component: PushImageButtonComponent;
@ -34,7 +29,7 @@ describe('PushImageButtonComponent (inline template)', () => {
fixture = TestBed.createComponent(PushImageButtonComponent); fixture = TestBed.createComponent(PushImageButtonComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.projectName = 'testing'; component.projectName = 'testing';
component.registryUrl = 'https://testing.harbor.com' component.registryUrl = 'https://testing.harbor.com';
serviceConfig = TestBed.get(SERVICE_CONFIG); serviceConfig = TestBed.get(SERVICE_CONFIG);
fixture.detectChanges(); fixture.detectChanges();
@ -57,9 +52,10 @@ describe('PushImageButtonComponent (inline template)', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(copyInputs.length).toEqual(2); expect(copyInputs.length).toEqual(2);
expect(copyInputs[0].value.trim()).toEqual(`docker tag SOURCE_IMAGE[:TAG] ${component.registryUrl}/${component.projectName}/IMAGE[:TAG]`); 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]`); expect(copyInputs[1].value.trim()).toEqual(`docker push ${component.registryUrl}/${component.projectName}/IMAGE[:TAG]`);
}) });
}); });
})); }));

View File

@ -1,49 +1,49 @@
import { Component, Input, ViewChild } from '@angular/core'; import { Component, Input, ViewChild } from "@angular/core";
import { CopyInputComponent } from './copy-input.component'; import { CopyInputComponent } from "./copy-input.component";
import { InlineAlertComponent } from '../inline-alert/inline-alert.component'; import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
@Component({ @Component({
selector: 'hbr-push-image-button', selector: "hbr-push-image-button",
templateUrl: './push-image.component.html', templateUrl: "./push-image.component.html",
styleUrls: ['./push-image.scss'], styleUrls: ["./push-image.scss"],
providers: [] providers: []
}) })
export class PushImageButtonComponent { export class PushImageButtonComponent {
@Input() registryUrl: string = "unknown"; @Input() registryUrl: string = "unknown";
@Input() projectName: string = "unknown"; @Input() projectName: string = "unknown";
@ViewChild("tagCopy") tagCopyInput: CopyInputComponent; @ViewChild("tagCopy") tagCopyInput: CopyInputComponent;
@ViewChild("pushCopy") pushCopyInput: CopyInputComponent; @ViewChild("pushCopy") pushCopyInput: CopyInputComponent;
@ViewChild("copyAlert") copyAlert: InlineAlertComponent; @ViewChild("copyAlert") copyAlert: InlineAlertComponent;
public get tagCommand(): string {
return `docker tag SOURCE_IMAGE[:TAG] ${this.registryUrl}/${
this.projectName
}/IMAGE[:TAG]`;
}
public get tagCommand(): string { public get pushCommand(): string {
return `docker tag SOURCE_IMAGE[:TAG] ${this.registryUrl}/${this.projectName}/IMAGE[:TAG]`; return `docker push ${this.registryUrl}/${this.projectName}/IMAGE[:TAG]`;
}
onclick(): void {
if (this.tagCopyInput) {
this.tagCopyInput.reset();
} }
public get pushCommand(): string { if (this.pushCopyInput) {
return `docker push ${this.registryUrl}/${this.projectName}/IMAGE[:TAG]`; this.pushCopyInput.reset();
} }
onclick(): void { if (this.copyAlert) {
if (this.tagCopyInput) { this.copyAlert.close();
this.tagCopyInput.reset();
}
if (this.pushCopyInput) {
this.pushCopyInput.reset();
}
if(this.copyAlert){
this.copyAlert.close();
}
} }
}
onCpError($event: any): void { onCpError($event: any): void {
if(this.copyAlert){ if (this.copyAlert) {
this.copyAlert.showInlineError("PUSH_IMAGE.COPY_ERROR"); this.copyAlert.showInlineError("PUSH_IMAGE.COPY_ERROR");
}
} }
}
} }

View File

@ -5,4 +5,4 @@ export * from './replication.component';
export const REPLICATION_DIRECTIVES: Type<any>[] = [ export const REPLICATION_DIRECTIVES: Type<any>[] = [
ReplicationComponent ReplicationComponent
]; ];

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="row flex-items-xs-between rightPos"> <div class="row flex-items-xs-between rightPos">
<div class="flex-xs-middle option-right"> <div class="flex-xs-middle option-right">
<hbr-filter [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filter)="doSearchRules($event)" [currentValue]="search.ruleName"></hbr-filter> <hbr-filter [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filterEvt)="doSearchRules($event)" [currentValue]="search.ruleName"></hbr-filter>
<span class="refresh-btn" (click)="refreshRules()"> <span class="refresh-btn" (click)="refreshRules()">
<clr-icon shape="refresh"></clr-icon> <clr-icon shape="refresh"></clr-icon>
</span> </span>
@ -18,7 +18,7 @@
<h5 class="flex-items-xs-bottom option-left-down" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5> <h5 class="flex-items-xs-bottom option-left-down" style="margin-left: 14px;">{{'REPLICATION.REPLICATION_JOBS' | translate}}</h5>
<div class="flex-items-xs-bottom option-right-down"> <div class="flex-items-xs-bottom option-right-down">
<button class="btn btn-link" (click)="toggleSearchJobOptionalName(currentJobSearchOption)">{{toggleJobSearchOption[currentJobSearchOption] | translate}}</button> <button class="btn btn-link" (click)="toggleSearchJobOptionalName(currentJobSearchOption)">{{toggleJobSearchOption[currentJobSearchOption] | translate}}</button>
<hbr-filter [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_JOBS_PLACEHOLDER" | translate}}' (filter)="doSearchJobs($event)" [currentValue]="search.repoName" ></hbr-filter> <hbr-filter [withDivider]="true" filterPlaceholder='{{"REPLICATION.FILTER_JOBS_PLACEHOLDER" | translate}}' (filterEvt)="doSearchJobs($event)" [currentValue]="search.repoName" ></hbr-filter>
<span class="refresh-btn" (click)="refreshJobs()"> <span class="refresh-btn" (click)="refreshJobs()">
<clr-icon shape="refresh"></clr-icon> <clr-icon shape="refresh"></clr-icon>
</span> </span>

View File

@ -1,4 +1,4 @@
import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing'; import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { NoopAnimationsModule } from "@angular/platform-browser/animations";
@ -9,7 +9,6 @@ import { ReplicationComponent } from './replication.component';
import { ListReplicationRuleComponent } from '../list-replication-rule/list-replication-rule.component'; import { ListReplicationRuleComponent } from '../list-replication-rule/list-replication-rule.component';
import { CreateEditRuleComponent } from '../create-edit-rule/create-edit-rule.component'; import { CreateEditRuleComponent } from '../create-edit-rule/create-edit-rule.component';
import { DatePickerComponent } from '../datetime-picker/datetime-picker.component'; import { DatePickerComponent } from '../datetime-picker/datetime-picker.component';
import { DateValidatorDirective } from '../datetime-picker/date-validator.directive';
import { FilterComponent } from '../filter/filter.component'; import { FilterComponent } from '../filter/filter.component';
import { InlineAlertComponent } from '../inline-alert/inline-alert.component'; import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
import {ReplicationRule, ReplicationJob, Endpoint} from '../service/interface'; import {ReplicationRule, ReplicationJob, Endpoint} from '../service/interface';
@ -20,7 +19,6 @@ import { ReplicationService, ReplicationDefaultService } from '../service/replic
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service'; import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component'; import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index'; import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index';
import {Project} from "../project-policy-config/project";
import {ProjectDefaultService, ProjectService} from "../service/project.service"; import {ProjectDefaultService, ProjectService} from "../service/project.service";
describe('Replication Component (inline template)', () => { describe('Replication Component (inline template)', () => {
@ -133,7 +131,7 @@ describe('Replication Component (inline template)', () => {
"repository": "library/busybox", "repository": "library/busybox",
"policy_id": 2, "policy_id": 2,
"operation": "transfer", "operation": "transfer",
"update_time": new Date("2017-04-23 12:20:33"), "update_time": new Date("2017-04-23 12:20:33"),
"tags": null "tags": null
} }
]; ];
@ -159,28 +157,28 @@ describe('Replication Component (inline template)', () => {
}, },
]; ];
let mockProjects: Project[] = [ // let mockProjects: Project[] = [
{ "project_id": 1, // { "project_id": 1,
"owner_id": 0, // "owner_id": 0,
"name": 'project_01', // "name": 'project_01',
"creation_time": '', // "creation_time": '',
"deleted": 0, // "deleted": 0,
"owner_name": '', // "owner_name": '',
"togglable": false, // "togglable": false,
"update_time": '', // "update_time": '',
"current_user_role_id": 0, // "current_user_role_id": 0,
"repo_count": 0, // "repo_count": 0,
"has_project_admin_role": false, // "has_project_admin_role": false,
"is_member": false, // "is_member": false,
"role_name": '', // "role_name": '',
"metadata": { // "metadata": {
"public": '', // "public": '',
"enable_content_trust": '', // "enable_content_trust": '',
"prevent_vul": '', // "prevent_vul": '',
"severity": '', // "severity": '',
"auto_scan": '', // "auto_scan": '',
} // }
}]; // }];
let mockJob: ReplicationJob = { let mockJob: ReplicationJob = {
metadata: {xTotalCount: 3}, metadata: {xTotalCount: 3},
@ -191,14 +189,14 @@ describe('Replication Component (inline template)', () => {
let fixtureCreate: ComponentFixture<CreateEditRuleComponent>; let fixtureCreate: ComponentFixture<CreateEditRuleComponent>;
let comp: ReplicationComponent; let comp: ReplicationComponent;
let compCreate: CreateEditRuleComponent; let compCreate: CreateEditRuleComponent;
let replicationService: ReplicationService; let replicationService: ReplicationService;
let endpointService: EndpointService; let endpointService: EndpointService;
let spyRules: jasmine.Spy; let spyRules: jasmine.Spy;
let spyJobs: jasmine.Spy; let spyJobs: jasmine.Spy;
let spyEndpoint: jasmine.Spy; let spyEndpoint: jasmine.Spy;
let deGrids: DebugElement[]; let deGrids: DebugElement[];
let deRules: DebugElement; let deRules: DebugElement;
let deJobs: DebugElement; let deJobs: DebugElement;
@ -211,9 +209,9 @@ describe('Replication Component (inline template)', () => {
replicationJobEndpoint: '/api/jobs/replication/testing' replicationJobEndpoint: '/api/jobs/replication/testing'
}; };
beforeEach(async(()=>{ beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
SharedModule, SharedModule,
NoopAnimationsModule NoopAnimationsModule
], ],
@ -256,9 +254,9 @@ describe('Replication Component (inline template)', () => {
spyEndpoint = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockEndpoints)); spyEndpoint = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockEndpoints));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
deGrids = fixture.debugElement.queryAll(del=>del.classes['datagrid']); deGrids = fixture.debugElement.queryAll(del => del.classes['datagrid']);
fixture.detectChanges(); fixture.detectChanges();
expect(deGrids).toBeTruthy(); expect(deGrids).toBeTruthy();
expect(deGrids.length).toEqual(2); expect(deGrids.length).toEqual(2);
@ -266,9 +264,9 @@ describe('Replication Component (inline template)', () => {
}); });
it('Should load replication rules', async(()=>{ it('Should load replication rules', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
deRules = deGrids[0].query(By.css('datagrid-cell')); deRules = deGrids[0].query(By.css('datagrid-cell'));
expect(deRules).toBeTruthy(); expect(deRules).toBeTruthy();
@ -279,9 +277,9 @@ describe('Replication Component (inline template)', () => {
}); });
})); }));
it('Should load replication jobs', async(()=>{ it('Should load replication jobs', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
deJobs = deGrids[1].query(By.css('datagrid-cell')); deJobs = deGrids[1].query(By.css('datagrid-cell'));
expect(deJobs).toBeTruthy(); expect(deJobs).toBeTruthy();
@ -293,21 +291,21 @@ describe('Replication Component (inline template)', () => {
}); });
})); }));
it('Should filter replication rules by keywords', async(()=>{ it('Should filter replication rules by keywords', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
comp.doSearchRules('sync_01'); comp.doSearchRules('sync_01');
fixture.detectChanges(); fixture.detectChanges();
let el: HTMLElement = deRules.nativeElement; let el: HTMLElement = deRules.nativeElement;
fixture.detectChanges(); fixture.detectChanges();
expect(el.textContent.trim()).toEqual('sync_01'); expect(el.textContent.trim()).toEqual('sync_01');
}); });
})); }));
it('Should filter replication jobs by keywords', async(()=>{ it('Should filter replication jobs by keywords', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
comp.doSearchJobs('nginx'); comp.doSearchJobs('nginx');
fixture.detectChanges(); fixture.detectChanges();
@ -318,9 +316,9 @@ describe('Replication Component (inline template)', () => {
}); });
})); }));
it('Should filter replication jobs by status', async(()=>{ it('Should filter replication jobs by status', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
comp.doFilterJobStatus('finished'); comp.doFilterJobStatus('finished');
let el: HTMLElement = deJobs.nativeElement; let el: HTMLElement = deJobs.nativeElement;
@ -330,16 +328,16 @@ describe('Replication Component (inline template)', () => {
}); });
})); }));
it('Should filter replication jobs by date range', async(()=>{ it('Should filter replication jobs by date range', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(()=>{ fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
comp.doJobSearchByStartTime('2017-05-01'); comp.doJobSearchByStartTime('2017-05-01');
comp.doJobSearchByEndTime('2015-05-25'); comp.doJobSearchByEndTime('2015-05-25');
let el: HTMLElement = deJobs.nativeElement; let el: HTMLElement = deJobs.nativeElement;
fixture.detectChanges(); fixture.detectChanges();
expect(el).toBeTruthy(); expect(el).toBeTruthy();
expect(el.textContent.trim()).toEqual('library/nginx'); expect(el.textContent.trim()).toEqual('library/nginx');
}); });
})) }));
}); });

View File

@ -11,17 +11,29 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, ViewChild, Input, Output, OnDestroy, EventEmitter } from '@angular/core'; import {
Component,
OnInit,
ViewChild,
Input,
Output,
OnDestroy,
EventEmitter
} from "@angular/core";
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from "@ngx-translate/core";
import { ListReplicationRuleComponent } from '../list-replication-rule/list-replication-rule.component'; import { ListReplicationRuleComponent } from "../list-replication-rule/list-replication-rule.component";
import { CreateEditRuleComponent } from '../create-edit-rule/create-edit-rule.component'; import { CreateEditRuleComponent } from "../create-edit-rule/create-edit-rule.component";
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from "../error-handler/error-handler";
import { ReplicationService } from '../service/replication.service'; import { ReplicationService } from "../service/replication.service";
import { RequestQueryParams } from '../service/RequestQueryParams'; import { RequestQueryParams } from "../service/RequestQueryParams";
import { ReplicationRule, ReplicationJob, ReplicationJobItem } from '../service/interface'; import {
ReplicationRule,
ReplicationJob,
ReplicationJobItem
} from "../service/interface";
import { import {
toPromise, toPromise,
@ -30,59 +42,68 @@ import {
doFiltering, doFiltering,
doSorting, doSorting,
calculatePage calculatePage
} from '../utils'; } from "../utils";
import { Comparator } from 'clarity-angular'; import { Comparator } from "clarity-angular";
import { JobLogViewerComponent } from '../job-log-viewer/index'; import { JobLogViewerComponent } from "../job-log-viewer/index";
import { State } from "clarity-angular"; import { State } from "clarity-angular";
import {Observable} from "rxjs/Observable"; import { Observable } from "rxjs/Observable";
import {Subscription} from "rxjs/Subscription"; import { Subscription } from "rxjs/Subscription";
import {ConfirmationTargets, ConfirmationButtons, ConfirmationState} from "../shared/shared.const"; import {
import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message"; ConfirmationTargets,
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message"; ConfirmationButtons,
import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component"; ConfirmationState
import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message"; } from "../shared/shared.const";
import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message";
import {
BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component";
import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message";
const ruleStatus: { [key: string]: any } = [ const ruleStatus: { [key: string]: any } = [
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' }, { key: "all", description: "REPLICATION.ALL_STATUS" },
{ 'key': '1', 'description': 'REPLICATION.ENABLED' }, { key: "1", description: "REPLICATION.ENABLED" },
{ 'key': '0', 'description': 'REPLICATION.DISABLED' } { key: "0", description: "REPLICATION.DISABLED" }
]; ];
const jobStatus: { [key: string]: any } = [ const jobStatus: { [key: string]: any } = [
{ 'key': 'all', 'description': 'REPLICATION.ALL' }, { key: "all", description: "REPLICATION.ALL" },
{ 'key': 'pending', 'description': 'REPLICATION.PENDING' }, { key: "pending", description: "REPLICATION.PENDING" },
{ 'key': 'running', 'description': 'REPLICATION.RUNNING' }, { key: "running", description: "REPLICATION.RUNNING" },
{ 'key': 'error', 'description': 'REPLICATION.ERROR' }, { key: "error", description: "REPLICATION.ERROR" },
{ 'key': 'retrying', 'description': 'REPLICATION.RETRYING' }, { key: "retrying", description: "REPLICATION.RETRYING" },
{ 'key': 'stopped', 'description': 'REPLICATION.STOPPED' }, { key: "stopped", description: "REPLICATION.STOPPED" },
{ 'key': 'finished', 'description': 'REPLICATION.FINISHED' }, { key: "finished", description: "REPLICATION.FINISHED" },
{ 'key': 'canceled', 'description': 'REPLICATION.CANCELED' } { key: "canceled", description: "REPLICATION.CANCELED" }
]; ];
const optionalSearch: {} = { 0: 'REPLICATION.ADVANCED', 1: 'REPLICATION.SIMPLE' }; const optionalSearch: {} = {
0: "REPLICATION.ADVANCED",
1: "REPLICATION.SIMPLE"
};
export class SearchOption { export class SearchOption {
ruleId: number | string; ruleId: number | string;
ruleName: string = ''; ruleName: string = "";
repoName: string = ''; repoName: string = "";
status: string = ''; status: string = "";
startTime: string = ''; startTime: string = "";
startTimestamp: string = ''; startTimestamp: string = "";
endTime: string = ''; endTime: string = "";
endTimestamp: string = ''; endTimestamp: string = "";
page: number = 1; page: number = 1;
pageSize: number = DEFAULT_PAGE_SIZE; pageSize: number = DEFAULT_PAGE_SIZE;
} }
@Component({ @Component({
selector: 'hbr-replication', selector: "hbr-replication",
templateUrl: './replication.component.html', templateUrl: "./replication.component.html",
styleUrls: ['./replication.component.scss'] styleUrls: ["./replication.component.scss"]
}) })
export class ReplicationComponent implements OnInit, OnDestroy { export class ReplicationComponent implements OnInit, OnDestroy {
@Input() projectId: number | string; @Input() projectId: number | string;
@Input() projectName: string; @Input() projectName: string;
@Input() isSystemAdmin: boolean; @Input() isSystemAdmin: boolean;
@ -96,10 +117,10 @@ export class ReplicationComponent implements OnInit, OnDestroy {
search: SearchOption = new SearchOption(); search: SearchOption = new SearchOption();
ruleStatus = ruleStatus; ruleStatus = ruleStatus;
currentRuleStatus: { key: string, description: string }; currentRuleStatus: { key: string; description: string };
jobStatus = jobStatus; jobStatus = jobStatus;
currentJobStatus: { key: string, description: string }; currentJobStatus: { key: string; description: string };
changedRules: ReplicationRule[]; changedRules: ReplicationRule[];
@ -108,7 +129,6 @@ export class ReplicationComponent implements OnInit, OnDestroy {
isStopOnGoing: boolean; isStopOnGoing: boolean;
hiddenJobList = true; hiddenJobList = true;
jobs: ReplicationJobItem[]; jobs: ReplicationJobItem[];
batchDelectionInfos: BatchInfo[] = []; batchDelectionInfos: BatchInfo[] = [];
@ -124,13 +144,17 @@ export class ReplicationComponent implements OnInit, OnDestroy {
@ViewChild("replicationLogViewer") @ViewChild("replicationLogViewer")
replicationLogViewer: JobLogViewerComponent; replicationLogViewer: JobLogViewerComponent;
@ViewChild('replicationConfirmDialog') @ViewChild("replicationConfirmDialog")
replicationConfirmDialog: ConfirmationDialogComponent; replicationConfirmDialog: ConfirmationDialogComponent;
creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date'); creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('update_time', 'date'); ReplicationJob
>("creation_time", "date");
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
ReplicationJob
>("update_time", "date");
//Server driven pagination // Server driven pagination
currentPage: number = 1; currentPage: number = 1;
totalCount: number = 0; totalCount: number = 0;
pageSize: number = DEFAULT_PAGE_SIZE; pageSize: number = DEFAULT_PAGE_SIZE;
@ -141,9 +165,8 @@ export class ReplicationComponent implements OnInit, OnDestroy {
constructor( constructor(
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private replicationService: ReplicationService, private replicationService: ReplicationService,
private translateService: TranslateService) { private translateService: TranslateService
} ) {}
public get showPaginationIndex(): boolean { public get showPaginationIndex(): boolean {
return this.totalCount > 0; return this.totalCount > 0;
@ -177,7 +200,7 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.goToRegistry.emit(); this.goToRegistry.emit();
} }
//Server driven data loading // Server driven data loading
clrLoadJobs(state: State): void { clrLoadJobs(state: State): void {
if (!state || !state.page || !this.search.ruleId) { if (!state || !state.page || !this.search.ruleId) {
return; return;
@ -185,66 +208,72 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.currentState = state; this.currentState = state;
let pageNumber: number = calculatePage(state); let pageNumber: number = calculatePage(state);
if (pageNumber <= 0) { pageNumber = 1; } if (pageNumber <= 0) {
pageNumber = 1;
}
let params: RequestQueryParams = new RequestQueryParams(); let params: RequestQueryParams = new RequestQueryParams();
//Pagination // Pagination
params.set("page", '' + pageNumber); params.set("page", "" + pageNumber);
params.set("page_size", '' + this.pageSize); params.set("page_size", "" + this.pageSize);
//Search by status // Search by status
if (this.search.status.trim()) { if (this.search.status.trim()) {
params.set('status', this.search.status); params.set("status", this.search.status);
} }
//Search by repository // Search by repository
if (this.search.repoName.trim()) { if (this.search.repoName.trim()) {
params.set('repository', this.search.repoName); params.set("repository", this.search.repoName);
} }
//Search by timestamps // Search by timestamps
if (this.search.startTimestamp.trim()) { if (this.search.startTimestamp.trim()) {
params.set('start_time', this.search.startTimestamp); params.set("start_time", this.search.startTimestamp);
} }
if (this.search.endTimestamp.trim()) { if (this.search.endTimestamp.trim()) {
params.set('end_time', this.search.endTimestamp); params.set("end_time", this.search.endTimestamp);
} }
this.jobsLoading = true; this.jobsLoading = true;
//Do filtering and sorting // Do filtering and sorting
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state); this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state); this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);
this.jobsLoading = false; this.jobsLoading = false;
toPromise<ReplicationJob>(this.replicationService toPromise<ReplicationJob>(
.getJobs(this.search.ruleId, params)) this.replicationService.getJobs(this.search.ruleId, params)
.then( )
response => { .then(response => {
this.totalCount = response.metadata.xTotalCount; this.totalCount = response.metadata.xTotalCount;
this.jobs = response.data; this.jobs = response.data;
if (!this.timerDelay) { if (!this.timerDelay) {
this.timerDelay = Observable.timer(10000, 10000).subscribe(() => { this.timerDelay = Observable.timer(10000, 10000).subscribe(() => {
let count: number = 0; let count: number = 0;
this.jobs.forEach((job) => { this.jobs.forEach(job => {
if ((job.status === 'pending') || (job.status === 'running') || (job.status === 'retrying')) { if (
count ++; job.status === "pending" ||
job.status === "running" ||
job.status === "retrying"
) {
count++;
} }
}); });
if (count > 0) { if (count > 0) {
this.clrLoadJobs(this.currentState); this.clrLoadJobs(this.currentState);
}else { } else {
this.timerDelay.unsubscribe(); this.timerDelay.unsubscribe();
this.timerDelay = null; this.timerDelay = null;
} }
}); });
} }
//Do filtering and sorting // Do filtering and sorting
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state); this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state); this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);
this.jobsLoading = false; this.jobsLoading = false;
})
}).catch(error => { .catch(error => {
this.jobsLoading = false; this.jobsLoading = false;
this.errorHandler.error(error); this.errorHandler.error(error);
}); });
@ -267,11 +296,11 @@ export class ReplicationComponent implements OnInit, OnDestroy {
selectOneRule(rule: ReplicationRule) { selectOneRule(rule: ReplicationRule) {
if (rule && rule.id) { if (rule && rule.id) {
this.hiddenJobList = false; this.hiddenJobList = false;
this.search.ruleId = rule.id || ''; this.search.ruleId = rule.id || "";
this.search.repoName = ''; this.search.repoName = "";
this.search.status = ''; this.search.status = "";
this.currentJobSearchOption = 0; this.currentJobSearchOption = 0;
this.currentJobStatus = { 'key': 'all', 'description': 'REPLICATION.ALL' }; this.currentJobStatus = { key: "all", description: "REPLICATION.ALL" };
this.loadFirstPage(); this.loadFirstPage();
} }
} }
@ -279,30 +308,35 @@ export class ReplicationComponent implements OnInit, OnDestroy {
replicateManualRule(rule: ReplicationRule) { replicateManualRule(rule: ReplicationRule) {
if (rule) { if (rule) {
this.batchDelectionInfos = []; this.batchDelectionInfos = [];
let initBatchMessage = new BatchInfo (); let initBatchMessage = new BatchInfo();
initBatchMessage.name = rule.name; initBatchMessage.name = rule.name;
this.batchDelectionInfos.push(initBatchMessage); this.batchDelectionInfos.push(initBatchMessage);
let replicationMessage = new ConfirmationMessage( let replicationMessage = new ConfirmationMessage(
'REPLICATION.REPLICATION_TITLE', "REPLICATION.REPLICATION_TITLE",
'REPLICATION.REPLICATION_SUMMARY', "REPLICATION.REPLICATION_SUMMARY",
rule.name, rule.name,
rule, rule,
ConfirmationTargets.TARGET, ConfirmationTargets.TARGET,
ConfirmationButtons.REPLICATE_CANCEL); ConfirmationButtons.REPLICATE_CANCEL
);
this.replicationConfirmDialog.open(replicationMessage); this.replicationConfirmDialog.open(replicationMessage);
} }
} }
confirmReplication(message: ConfirmationAcknowledgement) { confirmReplication(message: ConfirmationAcknowledgement) {
if (message && if (
message.source === ConfirmationTargets.TARGET && message &&
message.state === ConfirmationState.CONFIRMED) { message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED
) {
let rule: ReplicationRule = message.data; let rule: ReplicationRule = message.data;
if (rule) { if (rule) {
Promise.all([this.replicationOperate(+rule.id, rule.name)]).then((item) => { Promise.all([this.replicationOperate(+rule.id, rule.name)]).then(
this.selectOneRule(rule); item => {
}); this.selectOneRule(rule);
}
);
} }
} }
} }
@ -311,23 +345,33 @@ export class ReplicationComponent implements OnInit, OnDestroy {
let findedList = this.batchDelectionInfos.find(data => data.name === name); let findedList = this.batchDelectionInfos.find(data => data.name === name);
return toPromise<any>(this.replicationService.replicateRule(ruleId)) return toPromise<any>(this.replicationService.replicateRule(ruleId))
.then(response => { .then(response => {
this.translateService.get('BATCH.REPLICATE_SUCCESS') this.translateService
.subscribe(res => findedList = BathInfoChanges(findedList, res)); .get("BATCH.REPLICATE_SUCCESS")
}) .subscribe(res => (findedList = BathInfoChanges(findedList, res)));
.catch(error => { })
if (error && error.status === 412) { .catch(error => {
Observable.forkJoin(this.translateService.get('BATCH.REPLICATE_FAILURE'), if (error && error.status === 412) {
this.translateService.get('REPLICATION.REPLICATE_SUMMARY_FAILURE')) Observable.forkJoin(
.subscribe(function (res) { this.translateService.get("BATCH.REPLICATE_FAILURE"),
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]); this.translateService.get("REPLICATION.REPLICATE_SUMMARY_FAILURE")
}); ).subscribe(function(res) {
} else { findedList = BathInfoChanges(
this.translateService.get('BATCH.REPLICATE_FAILURE').subscribe(res => { findedList,
res[0],
false,
true,
res[1]
);
});
} else {
this.translateService
.get("BATCH.REPLICATE_FAILURE")
.subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true); findedList = BathInfoChanges(findedList, res, false, true);
}); });
} }
}); });
} }
customRedirect(rule: ReplicationRule) { customRedirect(rule: ReplicationRule) {
@ -344,12 +388,11 @@ export class ReplicationComponent implements OnInit, OnDestroy {
let status = $event.target["value"]; let status = $event.target["value"];
this.currentJobStatus = this.jobStatus.find((r: any) => r.key === status); this.currentJobStatus = this.jobStatus.find((r: any) => r.key === status);
if (this.currentJobStatus.key === 'all') { if (this.currentJobStatus.key === "all") {
status = ''; status = "";
} }
this.search.status = status; this.search.status = status;
this.doSearchJobs(this.search.repoName); this.doSearchJobs(this.search.repoName);
} }
} }
@ -368,17 +411,17 @@ export class ReplicationComponent implements OnInit, OnDestroy {
if (this.jobs && this.jobs.length) { if (this.jobs && this.jobs.length) {
this.isStopOnGoing = true; this.isStopOnGoing = true;
toPromise(this.replicationService.stopJobs(this.jobs[0].policy_id)) toPromise(this.replicationService.stopJobs(this.jobs[0].policy_id))
.then(res => { .then(res => {
this.refreshJobs(); this.refreshJobs();
this.isStopOnGoing = false; this.isStopOnGoing = false;
}) })
.catch(error => this.errorHandler.error(error)); .catch(error => this.errorHandler.error(error));
} }
} }
reloadRules(isReady: boolean) { reloadRules(isReady: boolean) {
if (isReady) { if (isReady) {
this.search.ruleName = ''; this.search.ruleName = "";
this.listReplicationRule.retrieveRules(this.search.ruleName); this.listReplicationRule.retrieveRules(this.search.ruleName);
} }
} }
@ -387,11 +430,10 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.listReplicationRule.retrieveRules(); this.listReplicationRule.retrieveRules();
} }
refreshJobs() { refreshJobs() {
this.currentJobStatus = this.jobStatus[0]; this.currentJobStatus = this.jobStatus[0];
this.search.startTime = ' '; this.search.startTime = " ";
this.search.endTime = ' '; this.search.endTime = " ";
this.search.repoName = ""; this.search.repoName = "";
this.search.startTimestamp = ""; this.search.startTimestamp = "";
this.search.endTimestamp = ""; this.search.endTimestamp = "";
@ -410,7 +452,9 @@ export class ReplicationComponent implements OnInit, OnDestroy {
} }
toggleSearchJobOptionalName(option: number) { toggleSearchJobOptionalName(option: number) {
(option === 1) ? this.currentJobSearchOption = 0 : this.currentJobSearchOption = 1; option === 1
? (this.currentJobSearchOption = 0)
: (this.currentJobSearchOption = 1);
} }
doJobSearchByStartTime(fromTimestamp: string) { doJobSearchByStartTime(fromTimestamp: string) {
@ -428,4 +472,4 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.replicationLogViewer.open(jobId); this.replicationLogViewer.open(jobId);
} }
} }
} }

View File

@ -5,4 +5,4 @@ export * from "./repository-gridview.component";
export const REPOSITORY_GRIDVIEW_DIRECTIVES: Type<any>[] = [ export const REPOSITORY_GRIDVIEW_DIRECTIVES: Type<any>[] = [
RepositoryGridviewComponent RepositoryGridviewComponent
]; ];

View File

@ -4,7 +4,7 @@
<div class="row flex-items-xs-right option-right rightPos"> <div class="row flex-items-xs-right option-right rightPos">
<div class="flex-xs-middle"> <div class="flex-xs-middle">
<hbr-push-image-button style="display: inline-block;" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button> <hbr-push-image-button style="display: inline-block;" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
<hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)" [currentValue]="lastFilteredRepoName"></hbr-filter> <hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filterEvt)="doSearchRepoNames($event)" [currentValue]="lastFilteredRepoName"></hbr-filter>
<span class="card-btn" (click)="showCard(true)" (mouseenter) ="mouseEnter('card') " (mouseleave) ="mouseLeave('card')"> <span class="card-btn" (click)="showCard(true)" (mouseenter) ="mouseEnter('card') " (mouseleave) ="mouseLeave('card')">
<clr-icon [ngClass]="{'is-highlight': isCardView || isHovering('card') }" shape="view-cards"></clr-icon> <clr-icon [ngClass]="{'is-highlight': isCardView || isHovering('card') }" shape="view-cards"></clr-icon>
</span> </span>

View File

@ -11,26 +11,23 @@ import { TagComponent } from '../tag/tag.component';
import { FilterComponent } from '../filter/filter.component'; import { FilterComponent } from '../filter/filter.component';
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from '../error-handler/error-handler';
import { Repository, RepositoryItem, Tag, SystemInfo } from '../service/interface'; import { Repository, RepositoryItem, SystemInfo } from '../service/interface';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service'; import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
import { TagService, TagDefaultService } from '../service/tag.service'; import { TagService, TagDefaultService } from '../service/tag.service';
import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service'; import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service';
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index'; import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
import { HBR_GRIDVIEW_DIRECTIVES } from '../gridview/index' import { HBR_GRIDVIEW_DIRECTIVES } from '../gridview/index';
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index'; import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index'; import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
import { JobLogViewerComponent } from '../job-log-viewer/index'; import { JobLogViewerComponent } from '../job-log-viewer/index';
import {LabelPieceComponent} from "../label-piece/label-piece.component"; import {LabelPieceComponent} from "../label-piece/label-piece.component";
import { click } from '../utils';
describe('RepositoryComponentGridview (inline template)', () => { describe('RepositoryComponentGridview (inline template)', () => {
let compRepo: RepositoryGridviewComponent; let compRepo: RepositoryGridviewComponent;
let fixtureRepo: ComponentFixture<RepositoryGridviewComponent>; let fixtureRepo: ComponentFixture<RepositoryGridviewComponent>;
let repositoryService: RepositoryService; let repositoryService: RepositoryService;
let tagService: TagService;
let systemInfoService: SystemInfoService; let systemInfoService: SystemInfoService;
let spyRepos: jasmine.Spy; let spyRepos: jasmine.Spy;
@ -74,20 +71,20 @@ describe('RepositoryComponentGridview (inline template)', () => {
data: mockRepoData data: mockRepoData
}; };
let mockTagData: Tag[] = [ // let mockTagData: Tag[] = [
{ // {
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", // "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
"name": "1.11.5", // "name": "1.11.5",
"size": "2049", // "size": "2049",
"architecture": "amd64", // "architecture": "amd64",
"os": "linux", // "os": "linux",
"docker_version": "1.12.3", // "docker_version": "1.12.3",
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"", // "author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
"created": new Date("2016-11-08T22:41:15.912313785Z"), // "created": new Date("2016-11-08T22:41:15.912313785Z"),
"signature": null, // "signature": null,
"labels": [] // "labels": []
} // }
]; // ];
let config: IServiceConfig = { let config: IServiceConfig = {
repositoryBaseEndpoint: '/api/repository/testing', repositoryBaseEndpoint: '/api/repository/testing',

View File

@ -1,36 +1,68 @@
import { Component, Input, Output, OnInit, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; import {
import { Router } from '@angular/router'; Component,
import { Subscription } from 'rxjs/Subscription'; Input,
import {Observable} from "rxjs/Observable"; Output,
import { TranslateService } from '@ngx-translate/core'; OnInit,
import { Comparator, State } from 'clarity-angular'; ViewChild,
ChangeDetectionStrategy,
import { Repository, SystemInfo, SystemInfoService, RepositoryService, RequestQueryParams, RepositoryItem, TagService } from '../service/index'; ChangeDetectorRef,
import { ErrorHandler } from '../error-handler/error-handler'; EventEmitter,
import { toPromise, CustomComparator , DEFAULT_PAGE_SIZE, calculatePage, doFiltering, doSorting, clone} from '../utils'; OnChanges,
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const'; SimpleChanges
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; } from "@angular/core";
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message'; import { Router } from "@angular/router";
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message'; import { Observable } from "rxjs/Observable";
import { Tag, CardItemEvent } from '../service/interface'; import { TranslateService } from "@ngx-translate/core";
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message"; import { Comparator, State } from "clarity-angular";
import { GridViewComponent } from '../gridview/grid-view.component';
import {
Repository,
SystemInfo,
SystemInfoService,
RepositoryService,
RequestQueryParams,
RepositoryItem,
TagService
} from "../service/index";
import { ErrorHandler } from "../error-handler/error-handler";
import {
toPromise,
CustomComparator,
DEFAULT_PAGE_SIZE,
calculatePage,
doFiltering,
doSorting,
clone
} from "../utils";
import {
ConfirmationState,
ConfirmationTargets,
ConfirmationButtons
} from "../shared/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";
import { Tag } from "../service/interface";
import {
BatchInfo,
BathInfoChanges
} from "../confirmation-dialog/confirmation-batch-message";
import { GridViewComponent } from "../gridview/grid-view.component";
@Component({ @Component({
selector: 'hbr-repository-gridview', selector: "hbr-repository-gridview",
templateUrl: './repository-gridview.component.html', templateUrl: "./repository-gridview.component.html",
styleUrls: ['./repository-gridview.component.scss'], styleUrls: ["./repository-gridview.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class RepositoryGridviewComponent implements OnChanges, OnInit { export class RepositoryGridviewComponent implements OnChanges, OnInit {
signedCon: {[key: string]: any | string[]} = {}; signedCon: { [key: string]: any | string[] } = {};
@Input() projectId: number; @Input() projectId: number;
@Input() projectName = 'unknown'; @Input() projectName = "unknown";
@Input() urlPrefix: string; @Input() urlPrefix: string;
@Input() hasSignedIn: boolean; @Input() hasSignedIn: boolean;
@Input() hasProjectAdminRole: boolean; @Input() hasProjectAdminRole: boolean;
@Input() mode = 'admiral'; @Input() mode = "admiral";
@Output() repoClickEvent = new EventEmitter<RepositoryItem>(); @Output() repoClickEvent = new EventEmitter<RepositoryItem>();
@Output() repoProvisionEvent = new EventEmitter<RepositoryItem>(); @Output() repoProvisionEvent = new EventEmitter<RepositoryItem>();
@Output() addInfoEvent = new EventEmitter<RepositoryItem>(); @Output() addInfoEvent = new EventEmitter<RepositoryItem>();
@ -47,20 +79,22 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
listHover = false; listHover = false;
batchDelectionInfos: BatchInfo[] = []; batchDelectionInfos: BatchInfo[] = [];
pullCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('pull_count', 'number'); pullCountComparator: Comparator<RepositoryItem> = new CustomComparator<
tagsCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('tags_count', 'number'); RepositoryItem
>("pull_count", "number");
tagsCountComparator: Comparator<RepositoryItem> = new CustomComparator<
RepositoryItem
>("tags_count", "number");
pageSize: number = DEFAULT_PAGE_SIZE; pageSize: number = DEFAULT_PAGE_SIZE;
currentPage = 1; currentPage = 1;
totalCount = 0; totalCount = 0;
currentState: State; currentState: State;
@ViewChild('confirmationDialog') @ViewChild("confirmationDialog")
confirmationDialog: ConfirmationDialogComponent; confirmationDialog: ConfirmationDialogComponent;
@ViewChild('gridView') @ViewChild("gridView") gridView: GridViewComponent;
gridView: GridViewComponent;
constructor( constructor(
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
@ -69,10 +103,11 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
private systemInfoService: SystemInfoService, private systemInfoService: SystemInfoService,
private tagService: TagService, private tagService: TagService,
private ref: ChangeDetectorRef, private ref: ChangeDetectorRef,
private router: Router) { } private router: Router
) {}
public get registryUrl(): string { public get registryUrl(): string {
return this.systemInfo ? this.systemInfo.registry_url : ''; return this.systemInfo ? this.systemInfo.registry_url : "";
} }
public get withClair(): boolean { public get withClair(): boolean {
@ -80,13 +115,15 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
public get isClairDBReady(): boolean { public get isClairDBReady(): boolean {
return this.systemInfo && return (
this.systemInfo &&
this.systemInfo.clair_vulnerability_status && this.systemInfo.clair_vulnerability_status &&
this.systemInfo.clair_vulnerability_status.overall_last_update > 0; this.systemInfo.clair_vulnerability_status.overall_last_update > 0
);
} }
public get withAdmiral(): boolean { public get withAdmiral(): boolean {
return this.mode === 'admiral' return this.mode === "admiral";
} }
public get showDBStatusWarning(): boolean { public get showDBStatusWarning(): boolean {
@ -94,7 +131,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes['projectId'] && changes['projectId'].currentValue) { if (changes["projectId"] && changes["projectId"].currentValue) {
this.refresh(); this.refresh();
} }
} }
@ -102,31 +139,32 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
ngOnInit(): void { ngOnInit(): void {
// Get system info for tag views // Get system info for tag views
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo()) toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then(systemInfo => this.systemInfo = systemInfo) .then(systemInfo => (this.systemInfo = systemInfo))
.catch(error => this.errorHandler.error(error)); .catch(error => this.errorHandler.error(error));
if (this.mode === 'admiral') { if (this.mode === "admiral") {
this.isCardView = true; this.isCardView = true;
} else { } else {
this.isCardView = false; this.isCardView = false;
} }
this.lastFilteredRepoName = ''; this.lastFilteredRepoName = "";
} }
confirmDeletion(message: ConfirmationAcknowledgement) { confirmDeletion(message: ConfirmationAcknowledgement) {
if (message && if (
message.source === ConfirmationTargets.REPOSITORY && message &&
message.state === ConfirmationState.CONFIRMED) { message.source === ConfirmationTargets.REPOSITORY &&
message.state === ConfirmationState.CONFIRMED
) {
let promiseLists: any[] = []; let promiseLists: any[] = [];
let repoNames: string[] = message.data.split(','); let repoNames: string[] = message.data.split(",");
repoNames.forEach(repoName => { repoNames.forEach(repoName => {
promiseLists.push(this.delOperate(repoName)); promiseLists.push(this.delOperate(repoName));
}); });
Promise.all(promiseLists).then((item) => { Promise.all(promiseLists).then(item => {
this.selectedRow = []; this.selectedRow = [];
this.refresh(); this.refresh();
let st: State = this.getStateAfterDeletion(); let st: State = this.getStateAfterDeletion();
@ -139,40 +177,61 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
} }
delOperate(repoName: string) { delOperate(repoName: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === repoName); let findedList = this.batchDelectionInfos.find(
data => data.name === repoName
);
if (this.signedCon[repoName].length !== 0) { if (this.signedCon[repoName].length !== 0) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), Observable.forkJoin(
this.translateService.get('REPOSITORY.DELETION_TITLE_REPO_SIGNED')).subscribe(res => { this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get("REPOSITORY.DELETION_TITLE_REPO_SIGNED")
).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]); findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
}); });
} else { } else {
return toPromise<number>(this.repositoryService return toPromise<number>(
.deleteRepository(repoName)) this.repositoryService.deleteRepository(repoName)
.then( )
response => { .then(response => {
this.translateService.get('BATCH.DELETED_SUCCESS').subscribe(res => { this.translateService.get("BATCH.DELETED_SUCCESS").subscribe(res => {
findedList = BathInfoChanges(findedList, res); findedList = BathInfoChanges(findedList, res);
});
}).catch(error => {
if (error.status === "412") {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPOSITORY.TAGS_SIGNED')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
return;
}
if (error.status === 503) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
return;
}
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
}); });
})
.catch(error => {
if (error.status === "412") {
Observable.forkJoin(
this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get("REPOSITORY.TAGS_SIGNED")
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
});
return;
}
if (error.status === 503) {
Observable.forkJoin(
this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get("REPOSITORY.TAGS_NO_DELETE")
).subscribe(res => {
findedList = BathInfoChanges(
findedList,
res[0],
false,
true,
res[1]
);
});
return;
}
this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
});
} }
} }
@ -189,7 +248,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
this.clrLoad(st); this.clrLoad(st);
} }
saveSignatures(event: {[key: string]: string[]}): void { saveSignatures(event: { [key: string]: string[] }): void {
Object.assign(this.signedCon, event); Object.assign(this.signedCon, event);
} }
@ -211,68 +270,92 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
}); });
Promise.all(repArr).then(() => { Promise.all(repArr).then(() => {
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO', '', repoNames.join(','), 'REPOSITORY.DELETION_SUMMARY_REPO', ConfirmationButtons.DELETE_CANCEL); this.confirmationDialogSet(
"REPOSITORY.DELETION_TITLE_REPO",
"",
repoNames.join(","),
"REPOSITORY.DELETION_SUMMARY_REPO",
ConfirmationButtons.DELETE_CANCEL
);
}); });
} }
} }
getTagInfo(repoName: string): Promise<void> { getTagInfo(repoName: string): Promise<void> {
this.signedCon[repoName] = []; this.signedCon[repoName] = [];
return toPromise<Tag[]>(this.tagService return toPromise<Tag[]>(this.tagService.getTags(repoName))
.getTags(repoName)) .then(items => {
.then(items => { items.forEach((t: Tag) => {
items.forEach((t: Tag) => { if (t.signature !== null) {
if (t.signature !== null) { this.signedCon[repoName].push(t.name);
this.signedCon[repoName].push(t.name); }
} });
}); })
}) .catch(error => this.errorHandler.error(error));
.catch(error => this.errorHandler.error(error));
} }
signedDataSet(repoName: string): void { signedDataSet(repoName: string): void {
let signature = ''; let signature = "";
if (this.signedCon[repoName].length === 0) { if (this.signedCon[repoName].length === 0) {
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO', ConfirmationButtons.DELETE_CANCEL); this.confirmationDialogSet(
"REPOSITORY.DELETION_TITLE_REPO",
signature,
repoName,
"REPOSITORY.DELETION_SUMMARY_REPO",
ConfirmationButtons.DELETE_CANCEL
);
return; return;
} }
signature = this.signedCon[repoName].join(','); signature = this.signedCon[repoName].join(",");
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO_SIGNED', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO_SIGNED', ConfirmationButtons.CLOSE); this.confirmationDialogSet(
"REPOSITORY.DELETION_TITLE_REPO_SIGNED",
signature,
repoName,
"REPOSITORY.DELETION_SUMMARY_REPO_SIGNED",
ConfirmationButtons.CLOSE
);
} }
confirmationDialogSet(summaryTitle: string, signature: string, repoName: string, summaryKey: string, button: ConfirmationButtons): void { confirmationDialogSet(
this.translateService.get(summaryKey, summaryTitle: string,
{ signature: string,
repoName: repoName, repoName: string,
signedImages: signature, summaryKey: string,
}) button: ConfirmationButtons
.subscribe((res: string) => { ): void {
summaryKey = res; this.translateService
let message = new ConfirmationMessage( .get(summaryKey, {
summaryTitle, repoName: repoName,
summaryKey, signedImages: signature
repoName, })
repoName, .subscribe((res: string) => {
ConfirmationTargets.REPOSITORY, summaryKey = res;
button); let message = new ConfirmationMessage(
this.confirmationDialog.open(message); summaryTitle,
summaryKey,
repoName,
repoName,
ConfirmationTargets.REPOSITORY,
button
);
this.confirmationDialog.open(message);
let hnd = setInterval(() => this.ref.markForCheck(), 100); let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 5000); setTimeout(() => clearInterval(hnd), 5000);
}); });
} }
provisionItemEvent(evt: any, repo: RepositoryItem): void { provisionItemEvent(evt: any, repo: RepositoryItem): void {
evt.stopPropagation(); evt.stopPropagation();
let repoCopy = clone(repo) let repoCopy = clone(repo);
repoCopy.name = this.registryUrl + ':443/' + repoCopy.name; repoCopy.name = this.registryUrl + ":443/" + repoCopy.name;
this.repoProvisionEvent.emit(repoCopy); this.repoProvisionEvent.emit(repoCopy);
} }
itemAddInfoEvent(evt: any, repo: RepositoryItem): void { itemAddInfoEvent(evt: any, repo: RepositoryItem): void {
evt.stopPropagation(); evt.stopPropagation();
let repoCopy = clone(repo) let repoCopy = clone(repo);
repoCopy.name = this.registryUrl + ':443/' + repoCopy.name; repoCopy.name = this.registryUrl + ":443/" + repoCopy.name;
this.addInfoEvent.emit(repoCopy); this.addInfoEvent.emit(repoCopy);
} }
@ -287,33 +370,42 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
refresh() { refresh() {
this.doSearchRepoNames(''); this.doSearchRepoNames("");
} }
loadNextPage() { loadNextPage() {
if (this.currentPage * this.pageSize >= this.totalCount) { if (this.currentPage * this.pageSize >= this.totalCount) {
return return;
} }
this.currentPage = this.currentPage + 1; this.currentPage = this.currentPage + 1;
// Pagination // Pagination
let params: RequestQueryParams = new RequestQueryParams(); let params: RequestQueryParams = new RequestQueryParams();
params.set("page", '' + this.currentPage); params.set("page", "" + this.currentPage);
params.set("page_size", '' + this.pageSize); params.set("page_size", "" + this.pageSize);
this.loading = true; this.loading = true;
toPromise<Repository>(this.repositoryService.getRepositories( toPromise<Repository>(
this.projectId, this.repositoryService.getRepositories(
this.lastFilteredRepoName, this.projectId,
params)) this.lastFilteredRepoName,
params
)
)
.then((repo: Repository) => { .then((repo: Repository) => {
this.totalCount = repo.metadata.xTotalCount; this.totalCount = repo.metadata.xTotalCount;
this.repositoriesCopy = repo.data; this.repositoriesCopy = repo.data;
this.signedCon = {}; this.signedCon = {};
// Do filtering and sorting // Do filtering and sorting
this.repositoriesCopy = doFiltering<RepositoryItem>(this.repositoriesCopy, this.currentState); this.repositoriesCopy = doFiltering<RepositoryItem>(
this.repositoriesCopy = doSorting<RepositoryItem>(this.repositoriesCopy, this.currentState); this.repositoriesCopy,
this.currentState
);
this.repositoriesCopy = doSorting<RepositoryItem>(
this.repositoriesCopy,
this.currentState
);
this.repositories = this.repositories.concat(this.repositoriesCopy); this.repositories = this.repositories.concat(this.repositoriesCopy);
this.loading = false; this.loading = false;
}) })
@ -321,8 +413,8 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
this.loading = false; this.loading = false;
this.errorHandler.error(error); this.errorHandler.error(error);
}); });
let hnd = setInterval(() => this.ref.markForCheck(), 500); let hnd = setInterval(() => this.ref.markForCheck(), 500);
setTimeout(() => clearInterval(hnd), 5000); setTimeout(() => clearInterval(hnd), 5000);
} }
clrLoad(state: State): void { clrLoad(state: State): void {
@ -331,26 +423,34 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
this.currentState = state; this.currentState = state;
let pageNumber: number = calculatePage(state); let pageNumber: number = calculatePage(state);
if (pageNumber <= 0) { pageNumber = 1; } if (pageNumber <= 0) {
pageNumber = 1;
}
// Pagination // Pagination
let params: RequestQueryParams = new RequestQueryParams(); let params: RequestQueryParams = new RequestQueryParams();
params.set("page", '' + pageNumber); params.set("page", "" + pageNumber);
params.set("page_size", '' + this.pageSize); params.set("page_size", "" + this.pageSize);
this.loading = true; this.loading = true;
toPromise<Repository>(this.repositoryService.getRepositories( toPromise<Repository>(
this.projectId, this.repositoryService.getRepositories(
this.lastFilteredRepoName, this.projectId,
params)) this.lastFilteredRepoName,
params
)
)
.then((repo: Repository) => { .then((repo: Repository) => {
this.totalCount = repo.metadata.xTotalCount; this.totalCount = repo.metadata.xTotalCount;
this.repositories = repo.data; this.repositories = repo.data;
this.signedCon = {}; this.signedCon = {};
// Do filtering and sorting // Do filtering and sorting
this.repositories = doFiltering<RepositoryItem>(this.repositories, state); this.repositories = doFiltering<RepositoryItem>(
this.repositories,
state
);
this.repositories = doSorting<RepositoryItem>(this.repositories, state); this.repositories = doSorting<RepositoryItem>(this.repositories, state);
this.loading = false; this.loading = false;
@ -367,7 +467,9 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
getStateAfterDeletion(): State { getStateAfterDeletion(): State {
let total: number = this.totalCount - 1; let total: number = this.totalCount - 1;
if (total <= 0) { return null; } if (total <= 0) {
return null;
}
let totalPages: number = Math.ceil(total / this.pageSize); let totalPages: number = Math.ceil(total / this.pageSize);
let targetPageNumber: number = this.currentPage; let targetPageNumber: number = this.currentPage;
@ -392,25 +494,25 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
getImgLink(repo: RepositoryItem): string { getImgLink(repo: RepositoryItem): string {
return '/container-image-icons?container-image=' + repo.name return "/container-image-icons?container-image=" + repo.name;
} }
getRepoDescrition(repo: RepositoryItem): string { getRepoDescrition(repo: RepositoryItem): string {
if (repo && repo.description) { if (repo && repo.description) {
return repo.description; return repo.description;
} }
return "No description for this repo. You can add it to this repository." return "No description for this repo. You can add it to this repository.";
} }
showCard(cardView: boolean) { showCard(cardView: boolean) {
if (this.isCardView === cardView) { if (this.isCardView === cardView) {
return return;
} }
this.isCardView = cardView; this.isCardView = cardView;
this.refresh(); this.refresh();
} }
mouseEnter(itemName: string) { mouseEnter(itemName: string) {
if (itemName === 'card') { if (itemName === "card") {
this.cardHover = true; this.cardHover = true;
} else { } else {
this.listHover = true; this.listHover = true;
@ -418,7 +520,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
mouseLeave(itemName: string) { mouseLeave(itemName: string) {
if (itemName === 'card') { if (itemName === "card") {
this.cardHover = false; this.cardHover = false;
} else { } else {
this.listHover = false; this.listHover = false;
@ -426,10 +528,10 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
} }
isHovering(itemName: string) { isHovering(itemName: string) {
if (itemName === 'card') { if (itemName === "card") {
return this.cardHover; return this.cardHover;
} else { } else {
return this.listHover; return this.listHover;
} }
} }
} }

View File

@ -1,6 +1,6 @@
import { ComponentFixture, TestBed, async, } from '@angular/core/testing'; import { ComponentFixture, TestBed, async, } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import {Component, DebugElement} from '@angular/core'; import { DebugElement} from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
@ -118,7 +118,7 @@ describe('RepositoryComponent (inline template)', () => {
project_id: 0, project_id: 0,
scope: "g", scope: "g",
update_time: "", update_time: "",
}] }];
let mockLabels1: Label[] = [{ let mockLabels1: Label[] = [{
color: "#9b0d54", color: "#9b0d54",
@ -139,7 +139,7 @@ describe('RepositoryComponent (inline template)', () => {
project_id: 1, project_id: 1,
scope: "p", scope: "p",
update_time: "", update_time: "",
}] }];
let config: IServiceConfig = { let config: IServiceConfig = {
repositoryBaseEndpoint: '/api/repository/testing', repositoryBaseEndpoint: '/api/repository/testing',

View File

@ -9,7 +9,7 @@ export interface IServiceConfig {
* Notary configurations * Notary configurations
* Registry configuration * Registry configuration
* Volume information * Volume information
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -22,8 +22,8 @@ export interface IServiceConfig {
* If the base endpoint is '/api/repositories', * If the base endpoint is '/api/repositories',
* the repository endpoint will be '/api/repositories/:repo_id', * the repository endpoint will be '/api/repositories/:repo_id',
* the tag(s) endpoint will be '/api/repositories/:repo_id/tags[/:tag_id]'. * the tag(s) endpoint will be '/api/repositories/:repo_id/tags[/:tag_id]'.
* *
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -31,7 +31,7 @@ export interface IServiceConfig {
/** /**
* The base endpoint of the service used to handle the recent access logs. * The base endpoint of the service used to handle the recent access logs.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -44,7 +44,7 @@ export interface IServiceConfig {
* If the base endpoint is '/api/endpoints', * If the base endpoint is '/api/endpoints',
* the endpoint for registry target will be '/api/endpoints/:endpoint_id', * the endpoint for registry target will be '/api/endpoints/:endpoint_id',
* the endpoint for pinging registry target will be '/api/endpoints/:endpoint_id/ping'. * the endpoint for pinging registry target will be '/api/endpoints/:endpoint_id/ping'.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -61,7 +61,7 @@ export interface IServiceConfig {
* E.g: * E.g:
* If the base endpoint is '/api/replication/rules', * If the base endpoint is '/api/replication/rules',
* the endpoint for rule will be '/api/replication/rules/:rule_id'. * the endpoint for rule will be '/api/replication/rules/:rule_id'.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -70,8 +70,8 @@ export interface IServiceConfig {
/** /**
* The base endpoint of the service used to handle the replication jobs. * The base endpoint of the service used to handle the replication jobs.
* *
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -79,7 +79,7 @@ export interface IServiceConfig {
/** /**
* The base endpoint of the service used to handle vulnerability scanning. * The base endpoint of the service used to handle vulnerability scanning.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -87,7 +87,7 @@ export interface IServiceConfig {
/** /**
* The base endpoint of the service used to handle project policy. * The base endpoint of the service used to handle project policy.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -102,7 +102,7 @@ export interface IServiceConfig {
/** /**
* To determine whether or not to enable the i18 multiple languages supporting. * To determine whether or not to enable the i18 multiple languages supporting.
* *
* @type {boolean} * @type {boolean}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -110,25 +110,25 @@ export interface IServiceConfig {
/** /**
* The cookie key used to store the current used language preference. * The cookie key used to store the current used language preference.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
langCookieKey?: string, langCookieKey?: string;
/** /**
* Declare what languages are supported. * Declare what languages are supported.
* *
* @type {string[]} * @type {string[]}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
supportedLangs?: string[], supportedLangs?: string[];
/** /**
* Define the default language the translate service uses. * Define the default language the translate service uses.
* *
* @type {string} * @type {string}
* @memberOf i18nConfig * @memberOf I18nConfig
*/ */
defaultLang?: string; defaultLang?: string;
@ -137,7 +137,7 @@ export interface IServiceConfig {
* Support two loaders: * Support two loaders:
* One is 'http', use async http to load json files with the specified url/path. * One is 'http', use async http to load json files with the specified url/path.
* Another is 'local', use local json variable to store the lang message. * Another is 'local', use local json variable to store the lang message.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -146,7 +146,7 @@ export interface IServiceConfig {
/** /**
* Define the basic url/path prefix for the loader to find the json files if the 'langMessageLoader' is 'http'. * Define the basic url/path prefix for the loader to find the json files if the 'langMessageLoader' is 'http'.
* For example, 'src/i18n/langs'. * For example, 'src/i18n/langs'.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -155,7 +155,7 @@ export interface IServiceConfig {
/** /**
* Define the suffix of the json file names without lang name if 'langMessageLoader' is 'http'. * Define the suffix of the json file names without lang name if 'langMessageLoader' is 'http'.
* For example, '-lang.json' is suffix of message file 'en-us-lang.json'. * For example, '-lang.json' is suffix of message file 'en-us-lang.json'.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -166,28 +166,28 @@ export interface IServiceConfig {
* this property must be defined to tell local JSON loader where to get the related messages. * this property must be defined to tell local JSON loader where to get the related messages.
* E.g: * E.g:
* If declare the following messages storage variables, * If declare the following messages storage variables,
* *
* export const EN_US_LANG: any = { * export const EN_US_LANG: any = {
* "APP_TITLE": { * "APP_TITLE": {
* "VMW_HARBOR": "VMware Harbor", * "VMW_HARBOR": "VMware Harbor",
* "HARBOR": "Harbor" * "HARBOR": "Harbor"
* } * }
* } * }
* *
* export const ZH_CN_LANG: any = { * export const ZH_CN_LANG: any = {
* "APP_TITLE": { * "APP_TITLE": {
* "VMW_HARBOR": "VMware Harbor中文版", * "VMW_HARBOR": "VMware Harbor中文版",
* "HARBOR": "Harbor" * "HARBOR": "Harbor"
* } * }
* } * }
* *
* then this property should be set to: * then this property should be set to:
* { * {
* "en-us": EN_US_LANG, * "en-us": EN_US_LANG,
* "zh-cn": ZH_CN_LANG * "zh-cn": ZH_CN_LANG
* }; * };
* *
* *
* @type {{ [key: string]: any }} * @type {{ [key: string]: any }}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -195,7 +195,7 @@ export interface IServiceConfig {
/** /**
* The base endpoint of configuration service. * The base endpoint of configuration service.
* *
* @type {string} * @type {string}
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
@ -203,7 +203,7 @@ export interface IServiceConfig {
/** /**
* The base endpoint of scan job service. * The base endpoint of scan job service.
* *
* @type {string} * @type {string}
* @memberof IServiceConfig * @memberof IServiceConfig
*/ */
@ -220,4 +220,4 @@ export interface IServiceConfig {
* @memberOf IServiceConfig * @memberOf IServiceConfig
*/ */
labelEndpoint?: string; labelEndpoint?: string;
} }

View File

@ -1,14 +1,15 @@
import { URLSearchParams } from '@angular/http'; import { URLSearchParams } from "@angular/http";
/** /**
* Wrap the class 'URLSearchParams' for future extending requirements. * Wrap the class 'URLSearchParams' for future extending requirements.
* Currently no extra methods provided. * Currently no extra methods provided.
* *
* @export * @export
* @class RequestQueryParams * @class RequestQueryParams
* @extends {URLSearchParams} * @extends {URLSearchParams}
*/ */
export class RequestQueryParams extends URLSearchParams { export class RequestQueryParams extends URLSearchParams {
constructor() {
constructor() { super(); } super();
} }
}

View File

@ -1,94 +1,112 @@
import { Observable } from 'rxjs/Observable'; import { Observable } from "rxjs/Observable";
import { RequestQueryParams } from './RequestQueryParams'; import { RequestQueryParams } from "./RequestQueryParams";
import { AccessLog, AccessLogItem } from './interface'; import { AccessLog, AccessLogItem } from "./interface";
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of'; import "rxjs/add/observable/of";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
import { Http, URLSearchParams } from '@angular/http'; import { Http } from "@angular/http";
import { buildHttpRequestOptions, HTTP_GET_OPTIONS } from '../utils'; import { buildHttpRequestOptions, HTTP_GET_OPTIONS } from "../utils";
/** /**
* Define service methods to handle the access log related things. * Define service methods to handle the access log related things.
* *
* @export * @export
* @abstract * @abstract
* @class AccessLogService * @class AccessLogService
*/ */
export abstract class AccessLogService { export abstract class AccessLogService {
/** /**
* Get the audit logs for the specified project. * Get the audit logs for the specified project.
* Set query parameters through 'queryParams', support: * Set query parameters through 'queryParams', support:
* - page * - page
* - pageSize * - pageSize
* *
* @abstract * @abstract
* @param {(number | string)} projectId * @param {(number | string)} projectId
* @param {RequestQueryParams} [queryParams] * @param {RequestQueryParams} [queryParams]
* @returns {(Observable<AccessLog> | Promise<AccessLog> | AccessLog)} * @returns {(Observable<AccessLog> | Promise<AccessLog> | AccessLog)}
* *
* @memberOf AccessLogService * @memberOf AccessLogService
*/ */
abstract getAuditLogs(projectId: number | string, queryParams?: RequestQueryParams): Observable<AccessLog> | Promise<AccessLog> | AccessLog; abstract getAuditLogs(
projectId: number | string,
queryParams?: RequestQueryParams
): Observable<AccessLog> | Promise<AccessLog> | AccessLog;
/** /**
* Get the recent logs. * Get the recent logs.
* *
* @abstract * @abstract
* @param {RequestQueryParams} [queryParams] * @param {RequestQueryParams} [queryParams]
* @returns {(Observable<AccessLog> | Promise<AccessLog> | AccessLog)} * @returns {(Observable<AccessLog> | Promise<AccessLog> | AccessLog)}
* *
* @memberOf AccessLogService * @memberOf AccessLogService
*/ */
abstract getRecentLogs(queryParams?: RequestQueryParams): Observable<AccessLog> | Promise<AccessLog> | AccessLog; abstract getRecentLogs(
queryParams?: RequestQueryParams
): Observable<AccessLog> | Promise<AccessLog> | AccessLog;
} }
/** /**
* Implement a default service for access log. * Implement a default service for access log.
* *
* @export * @export
* @class AccessLogDefaultService * @class AccessLogDefaultService
* @extends {AccessLogService} * @extends {AccessLogService}
*/ */
@Injectable() @Injectable()
export class AccessLogDefaultService extends AccessLogService { export class AccessLogDefaultService extends AccessLogService {
constructor( constructor(
private http: Http, private http: Http,
@Inject(SERVICE_CONFIG) private config: IServiceConfig) { @Inject(SERVICE_CONFIG) private config: IServiceConfig
super(); ) {
super();
}
public getAuditLogs(
projectId: number | string,
queryParams?: RequestQueryParams
): Observable<AccessLog> | Promise<AccessLog> | AccessLog {
return Observable.of({} as AccessLog);
}
public getRecentLogs(
queryParams?: RequestQueryParams
): Observable<AccessLog> | Promise<AccessLog> | AccessLog {
let url: string = this.config.logBaseEndpoint
? this.config.logBaseEndpoint
: "";
if (url === "") {
url = "/api/logs";
} }
public getAuditLogs(projectId: number | string, queryParams?: RequestQueryParams): Observable<AccessLog> | Promise<AccessLog> | AccessLog { return this.http
return Observable.of({}); .get(
} url,
queryParams ? buildHttpRequestOptions(queryParams) : HTTP_GET_OPTIONS
public getRecentLogs(queryParams?: RequestQueryParams): Observable<AccessLog> | Promise<AccessLog> | AccessLog { )
let url: string = this.config.logBaseEndpoint ? this.config.logBaseEndpoint : ""; .toPromise()
if (url === '') { .then(response => {
url = '/api/logs'; let result: AccessLog = {
metadata: {
xTotalCount: 0
},
data: []
};
let xHeader: string | null = "0";
if (response && response.headers) {
xHeader = response.headers.get("X-Total-Count");
} }
return this.http.get(url, queryParams ? buildHttpRequestOptions(queryParams) : HTTP_GET_OPTIONS).toPromise() if (result && result.metadata) {
.then(response => { result.metadata.xTotalCount = parseInt(xHeader ? xHeader : "0", 0);
let result: AccessLog = { if (result.metadata.xTotalCount > 0) {
metadata: { result.data = response.json() as AccessLogItem[];
xTotalCount: 0 }
}, }
data: []
};
let xHeader: string | null = "0";
if (response && response.headers) {
xHeader = response.headers.get("X-Total-Count");
}
if (result && result.metadata) { return result;
result.metadata.xTotalCount = parseInt(xHeader ? xHeader : "0", 0); })
if (result.metadata.xTotalCount > 0) { .catch(error => Promise.reject(error));
result.data = response.json() as AccessLogItem[]; }
} }
}
return result;
})
.catch(error => Promise.reject(error));
}
}

View File

@ -1,69 +1,84 @@
import { Observable } from 'rxjs/Observable';
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of'; import { Http } from "@angular/http";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { Observable } from "rxjs/Observable";
import { Http } from '@angular/http'; import "rxjs/add/observable/of";
import { HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from '../utils';
import { Configuration } from '../config/config';
import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
import { HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from "../utils";
import { Configuration } from "../config/config";
/** /**
* Service used to get and save registry-related configurations. * Service used to get and save registry-related configurations.
* *
* @export * @export
* @abstract * @abstract
* @class ConfigurationService * @class ConfigurationService
*/ */
export abstract class ConfigurationService { export abstract class ConfigurationService {
/**
* Get configurations.
*
* @abstract
* @returns {(Observable<Configuration> | Promise<Configuration> | Configuration)}
*
* @memberOf ConfigurationService
*/
abstract getConfigurations():
| Observable<Configuration>
| Promise<Configuration>
| Configuration;
/** /**
* Get configurations. * Save configurations.
* *
* @abstract * @abstract
* @returns {(Observable<Configuration> | Promise<Configuration> | Configuration)} * @returns {(Observable<Configuration> | Promise<Configuration> | Configuration)}
* *
* @memberOf ConfigurationService * @memberOf ConfigurationService
*/ */
abstract getConfigurations(): Observable<Configuration> | Promise<Configuration> | Configuration; abstract saveConfigurations(
changedConfigs: any | { [key: string]: any | any[] }
/** ): Observable<any> | Promise<any> | any;
* Save configurations.
*
* @abstract
* @returns {(Observable<Configuration> | Promise<Configuration> | Configuration)}
*
* @memberOf ConfigurationService
*/
abstract saveConfigurations(changedConfigs: any | { [key: string]: any | any[] }): Observable<any> | Promise<any> | any;
} }
@Injectable() @Injectable()
export class ConfigurationDefaultService extends ConfigurationService { export class ConfigurationDefaultService extends ConfigurationService {
_baseUrl: string; _baseUrl: string;
constructor( constructor(
private http: Http, private http: Http,
@Inject(SERVICE_CONFIG) private config: IServiceConfig) { @Inject(SERVICE_CONFIG) private config: IServiceConfig
super(); ) {
super();
this._baseUrl = this.config && this.config.configurationEndpoint ? this._baseUrl =
this.config.configurationEndpoint : "/api/configurations"; this.config && this.config.configurationEndpoint
? this.config.configurationEndpoint
: "/api/configurations";
}
getConfigurations():
| Observable<Configuration>
| Promise<Configuration>
| Configuration {
return this.http
.get(this._baseUrl, HTTP_GET_OPTIONS)
.toPromise()
.then(response => response.json() as Configuration)
.catch(error => Promise.reject(error));
}
saveConfigurations(
changedConfigs: any | { [key: string]: any | any[] }
): Observable<any> | Promise<any> | any {
if (!changedConfigs) {
return Promise.reject("Bad argument!");
} }
getConfigurations(): Observable<Configuration> | Promise<Configuration> | Configuration { return this.http
return this.http.get(this._baseUrl, HTTP_GET_OPTIONS).toPromise() .put(this._baseUrl, JSON.stringify(changedConfigs), HTTP_JSON_OPTIONS)
.then(response => response.json() as Configuration) .toPromise()
.catch(error => Promise.reject(error)); .then(() => {})
} .catch(error => Promise.reject(error));
}
saveConfigurations(changedConfigs: any | { [key: string]: any | any[] }): Observable<any> | Promise<any> | any { }
if (!changedConfigs) {
return Promise.reject("Bad argument!");
}
return this.http.put(this._baseUrl, JSON.stringify(changedConfigs), HTTP_JSON_OPTIONS)
.toPromise()
.then(() => { })
.catch(error => Promise.reject(error));
}
}

View File

@ -1,12 +1,13 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { EndpointService, EndpointDefaultService } from './endpoint.service';
import { IServiceConfig, SERVICE_CONFIG } from '../service.config'; import { IServiceConfig, SERVICE_CONFIG } from '../service.config';
import { EndpointService, EndpointDefaultService } from './endpoint.service';
describe('EndpointService', () => { describe('EndpointService', () => {
let mockEndpoint:IServiceConfig = { let mockEndpoint: IServiceConfig = {
targetBaseEndpoint: '/api/endpoint/testing' targetBaseEndpoint: '/api/endpoint/testing'
}; };

View File

@ -1,207 +1,244 @@
import { Observable } from 'rxjs/Observable';
import { RequestQueryParams } from './RequestQueryParams';
import { Endpoint, ReplicationRule } from './interface';
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import { Http } from '@angular/http'; import { Http } from "@angular/http";
import 'rxjs/add/observable/of'; import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/of";
import { IServiceConfig, SERVICE_CONFIG } from '../service.config'; import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
import {
import {buildHttpRequestOptions, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from '../utils'; buildHttpRequestOptions,
HTTP_JSON_OPTIONS,
HTTP_GET_OPTIONS
} from "../utils";
import { RequestQueryParams } from "./RequestQueryParams";
import { Endpoint, ReplicationRule } from "./interface";
/** /**
* Define the service methods to handle the endpoint related things. * Define the service methods to handle the endpoint related things.
* *
* @export * @export
* @abstract * @abstract
* @class EndpointService * @class EndpointService
*/ */
export abstract class EndpointService { export abstract class EndpointService {
/** /**
* Get all the endpoints. * Get all the endpoints.
* Set the argument 'endpointName' to return only the endpoints match the name pattern. * Set the argument 'endpointName' to return only the endpoints match the name pattern.
* *
* @abstract * @abstract
* @param {string} [endpointName] * @param {string} [endpointName]
* @param {RequestQueryParams} [queryParams] * @param {RequestQueryParams} [queryParams]
* @returns {(Observable<Endpoint[]> | Endpoint[])} * @returns {(Observable<Endpoint[]> | Endpoint[])}
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract getEndpoints(endpointName?: string, queryParams?: RequestQueryParams): Observable<Endpoint[]> | Promise<Endpoint[]> | Endpoint[]; abstract getEndpoints(
endpointName?: string,
queryParams?: RequestQueryParams
): Observable<Endpoint[]> | Promise<Endpoint[]> | Endpoint[];
/** /**
* Get the specified endpoint. * Get the specified endpoint.
* *
* @abstract * @abstract
* @param {(number | string)} endpointId * @param {(number | string)} endpointId
* @returns {(Observable<Endpoint> | Endpoint)} * @returns {(Observable<Endpoint> | Endpoint)}
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract getEndpoint(endpointId: number | string): Observable<Endpoint> | Promise<Endpoint> | Endpoint; abstract getEndpoint(
endpointId: number | string
): Observable<Endpoint> | Promise<Endpoint> | Endpoint;
/** /**
* Create new endpoint. * Create new endpoint.
* *
* @abstract * @abstract
* @param {Endpoint} endpoint * @param {Endpoint} endpoint
* @returns {(Observable<any> | any)} * @returns {(Observable<any> | any)}
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract createEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any; abstract createEndpoint(
endpoint: Endpoint
): Observable<any> | Promise<any> | any;
/** /**
* Update the specified endpoint. * Update the specified endpoint.
* *
* @abstract * @abstract
* @param {(number | string)} endpointId * @param {(number | string)} endpointId
* @param {Endpoint} endpoint * @param {Endpoint} endpoint
* @returns {(Observable<any> | any)} * @returns {(Observable<any> | any)}
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract updateEndpoint(endpointId: number | string, endpoint: Endpoint): Observable<any> | Promise<any> | any; abstract updateEndpoint(
endpointId: number | string,
endpoint: Endpoint
): Observable<any> | Promise<any> | any;
/** /**
* Delete the specified endpoint. * Delete the specified endpoint.
* *
* @abstract * @abstract
* @param {(number | string)} endpointId * @param {(number | string)} endpointId
* @returns {(Observable<any> | any)} * @returns {(Observable<any> | any)}
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract deleteEndpoint(endpointId: number | string): Observable<any> | Promise<any> | any; abstract deleteEndpoint(
endpointId: number | string
): Observable<any> | Promise<any> | any;
/** /**
* Ping the specified endpoint. * Ping the specified endpoint.
* *
* @abstract * @abstract
* @param {Endpoint} endpoint * @param {Endpoint} endpoint
* @returns {(Observable<any> | any)} * @returns {(Observable<any> | any)}
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract pingEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any; abstract pingEndpoint(
endpoint: Endpoint
): Observable<any> | Promise<any> | any;
/** /**
* Check endpoint whether in used with specific replication rule. * Check endpoint whether in used with specific replication rule.
* *
* @abstract * @abstract
* @param {{number | string}} endpointId * @param {{number | string}} endpointId
* @returns {{Observable<any> | any}} * @returns {{Observable<any> | any}}
*/ */
abstract getEndpointWithReplicationRules(endpointId: number | string): Observable<any> | Promise<any> | any; abstract getEndpointWithReplicationRules(
endpointId: number | string
): Observable<any> | Promise<any> | any;
} }
/** /**
* Implement default service for endpoint. * Implement default service for endpoint.
* *
* @export * @export
* @class EndpointDefaultService * @class EndpointDefaultService
* @extends {EndpointService} * @extends {EndpointService}
*/ */
@Injectable() @Injectable()
export class EndpointDefaultService extends EndpointService { export class EndpointDefaultService extends EndpointService {
_endpointUrl: string;
_endpointUrl: string;
constructor( constructor(
@Inject(SERVICE_CONFIG) config: IServiceConfig, @Inject(SERVICE_CONFIG) config: IServiceConfig,
private http: Http){ private http: Http
super(); ) {
this._endpointUrl = config.targetBaseEndpoint ? config.targetBaseEndpoint : '/api/targets'; super();
} this._endpointUrl = config.targetBaseEndpoint
? config.targetBaseEndpoint
: "/api/targets";
}
public getEndpoints(endpointName?: string, queryParams?: RequestQueryParams): Observable<Endpoint[]> | Promise<Endpoint[]> | Endpoint[] { public getEndpoints(
if(!queryParams) { endpointName?: string,
queryParams = new RequestQueryParams(); queryParams?: RequestQueryParams
} ): Observable<Endpoint[]> | Promise<Endpoint[]> | Endpoint[] {
if(endpointName) { if (!queryParams) {
queryParams.set('name', endpointName); queryParams = new RequestQueryParams();
}
let requestUrl: string = `${this._endpointUrl}`;
return this.http
.get(requestUrl, buildHttpRequestOptions(queryParams))
.toPromise()
.then(response=>response.json())
.catch(error=>Promise.reject(error));
} }
if (endpointName) {
queryParams.set("name", endpointName);
}
let requestUrl: string = `${this._endpointUrl}`;
return this.http
.get(requestUrl, buildHttpRequestOptions(queryParams))
.toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
}
public getEndpoint(endpointId: number | string): Observable<Endpoint> | Promise<Endpoint> | Endpoint { public getEndpoint(
if(!endpointId || endpointId <= 0) { endpointId: number | string
return Promise.reject('Bad request argument.'); ): Observable<Endpoint> | Promise<Endpoint> | Endpoint {
} if (!endpointId || endpointId <= 0) {
let requestUrl: string = `${this._endpointUrl}/${endpointId}`; return Promise.reject("Bad request argument.");
return this.http
.get(requestUrl, HTTP_GET_OPTIONS)
.toPromise()
.then(response=>response.json() as Endpoint)
.catch(error=>Promise.reject(error));
} }
let requestUrl: string = `${this._endpointUrl}/${endpointId}`;
return this.http
.get(requestUrl, HTTP_GET_OPTIONS)
.toPromise()
.then(response => response.json() as Endpoint)
.catch(error => Promise.reject(error));
}
public createEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any { public createEndpoint(
if(!endpoint) { endpoint: Endpoint
return Promise.reject('Invalid endpoint.'); ): Observable<any> | Promise<any> | any {
} if (!endpoint) {
let requestUrl: string = `${this._endpointUrl}`; return Promise.reject("Invalid endpoint.");
return this.http
.post(requestUrl, JSON.stringify(endpoint), HTTP_JSON_OPTIONS)
.toPromise()
.then(response=>response.status)
.catch(error=>Promise.reject(error));
} }
let requestUrl: string = `${this._endpointUrl}`;
return this.http
.post(requestUrl, JSON.stringify(endpoint), HTTP_JSON_OPTIONS)
.toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
public updateEndpoint(endpointId: number | string, endpoint: Endpoint): Observable<any> | Promise<any> | any { public updateEndpoint(
if(!endpointId || endpointId <= 0) { endpointId: number | string,
return Promise.reject('Bad request argument.'); endpoint: Endpoint
} ): Observable<any> | Promise<any> | any {
if(!endpoint) { if (!endpointId || endpointId <= 0) {
return Promise.reject('Invalid endpoint.'); return Promise.reject("Bad request argument.");
}
let requestUrl: string = `${this._endpointUrl}/${endpointId}`;
return this.http
.put(requestUrl, JSON.stringify(endpoint), HTTP_JSON_OPTIONS)
.toPromise()
.then(response=>response.status)
.catch(error=>Promise.reject(error));
} }
if (!endpoint) {
return Promise.reject("Invalid endpoint.");
}
let requestUrl: string = `${this._endpointUrl}/${endpointId}`;
return this.http
.put(requestUrl, JSON.stringify(endpoint), HTTP_JSON_OPTIONS)
.toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
public deleteEndpoint(endpointId: number | string): Observable<any> | Promise<any> | any { public deleteEndpoint(
if(!endpointId || endpointId <= 0) { endpointId: number | string
return Promise.reject('Bad request argument.'); ): Observable<any> | Promise<any> | any {
} if (!endpointId || endpointId <= 0) {
let requestUrl: string = `${this._endpointUrl}/${endpointId}`; return Promise.reject("Bad request argument.");
return this.http
.delete(requestUrl)
.toPromise()
.then(response=>response.status)
.catch(error=>Promise.reject(error));
} }
let requestUrl: string = `${this._endpointUrl}/${endpointId}`;
return this.http
.delete(requestUrl)
.toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
public pingEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any { public pingEndpoint(
if(!endpoint) { endpoint: Endpoint
return Promise.reject('Invalid endpoint.'); ): Observable<any> | Promise<any> | any {
} if (!endpoint) {
let requestUrl: string = `${this._endpointUrl}/ping`; return Promise.reject("Invalid endpoint.");
return this.http
.post(requestUrl, endpoint, HTTP_JSON_OPTIONS)
.toPromise()
.then(response=>response.status)
.catch(error=>Promise.reject(error));
} }
let requestUrl: string = `${this._endpointUrl}/ping`;
return this.http
.post(requestUrl, endpoint, HTTP_JSON_OPTIONS)
.toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
public getEndpointWithReplicationRules(endpointId: number | string): Observable<any> | Promise<any> | any { public getEndpointWithReplicationRules(
if(!endpointId || endpointId <= 0) { endpointId: number | string
return Promise.reject('Bad request argument.'); ): Observable<any> | Promise<any> | any {
} if (!endpointId || endpointId <= 0) {
let requestUrl: string = `${this._endpointUrl}/${endpointId}/policies`; return Promise.reject("Bad request argument.");
return this.http
.get(requestUrl, HTTP_GET_OPTIONS)
.toPromise()
.then(response=>response.json() as ReplicationRule[])
.catch(error=>Promise.reject(error));
} }
} let requestUrl: string = `${this._endpointUrl}/${endpointId}/policies`;
return this.http
.get(requestUrl, HTTP_GET_OPTIONS)
.toPromise()
.then(response => response.json() as ReplicationRule[])
.catch(error => Promise.reject(error));
}
}

View File

@ -1,291 +1,298 @@
import {Project} from "../project-policy-config/project"; import { Project } from "../project-policy-config/project";
/** /**
* The base interface contains the general properties * The base interface contains the general properties
* *
* @export * @export
* @interface Base * @interface Base
*/ */
export interface Base { export interface Base {
id?: string | number; id?: string | number;
name?: string; name?: string;
creation_time?: Date; creation_time?: Date;
update_time?: Date; update_time?: Date;
} }
/** /**
* Interface for Repository Info * Interface for Repository Info
* *
* @export * @export
* @interface Repository * @interface Repository
* @extends {Base} * @extends {Base}
*/ */
export interface RepositoryItem extends Base { export interface RepositoryItem extends Base {
[key: string]: any | any[] [key: string]: any | any[];
name: string; name: string;
tags_count: number; tags_count: number;
owner_id?: number; owner_id?: number;
project_id?: number; project_id?: number;
description?: string; description?: string;
star_count?: number; star_count?: number;
pull_count?: number; pull_count?: number;
} }
/** /**
* Interface for repository * Interface for repository
* *
* @export * @export
* @interface Repository * @interface Repository
*/ */
export interface Repository { export interface Repository {
metadata?: Metadata; metadata?: Metadata;
data: RepositoryItem[]; data: RepositoryItem[];
} }
/** /**
* Interface for the tag of repository * Interface for the tag of repository
* *
* @export * @export
* @interface Tag * @interface Tag
* @extends {Base} * @extends {Base}
*/ */
export interface Tag extends Base { export interface Tag extends Base {
digest: string; digest: string;
name: string; name: string;
size: string; size: string;
architecture: string; architecture: string;
os: string; os: string;
docker_version: string; docker_version: string;
author: string; author: string;
created: Date; created: Date;
signature?: string; signature?: string;
scan_overview?: VulnerabilitySummary; scan_overview?: VulnerabilitySummary;
labels: Label[]; labels: Label[];
} }
/** /**
* Interface for registry endpoints. * Interface for registry endpoints.
* *
* @export * @export
* @interface Endpoint * @interface Endpoint
* @extends {Base} * @extends {Base}
*/ */
export interface Endpoint extends Base { export interface Endpoint extends Base {
endpoint: string; endpoint: string;
name: string; name: string;
username?: string; username?: string;
password?: string; password?: string;
insecure: boolean; insecure: boolean;
type: number; type: number;
[key: string]: any; [key: string]: any;
} }
/** /**
* Interface for replication rule. * Interface for replication rule.
* *
* @export * @export
* @interface ReplicationRule * @interface ReplicationRule
* @interface Filter * @interface Filter
* @interface Trigger * @interface Trigger
*/ */
export interface ReplicationRule extends Base { export interface ReplicationRule extends Base {
[key: string]: any; [key: string]: any;
id?: number; id?: number;
name: string; name: string;
description: string; description: string;
projects: Project[]; projects: Project[];
targets: Endpoint[] ; targets: Endpoint[];
trigger: Trigger ; trigger: Trigger;
filters: Filter[] ; filters: Filter[];
replicate_existing_image_now?: boolean; replicate_existing_image_now?: boolean;
replicate_deletion?: boolean; replicate_deletion?: boolean;
} }
export class Filter { export class Filter {
kind: string; kind: string;
pattern: string; pattern: string;
constructor(kind: string, pattern: string) { constructor(kind: string, pattern: string) {
this.kind = kind; this.kind = kind;
this.pattern = pattern; this.pattern = pattern;
} }
} }
export class Trigger { export class Trigger {
kind: string; kind: string;
schedule_param: any | { schedule_param:
| any
| {
[key: string]: any | any[]; [key: string]: any | any[];
}; };
constructor(kind: string, param: any | { [key: string]: any | any[]; }) { constructor(kind: string, param: any | { [key: string]: any | any[] }) {
this.kind = kind; this.kind = kind;
this.schedule_param = param; this.schedule_param = param;
} }
} }
/** /**
* Interface for replication job. * Interface for replication job.
* *
* @export * @export
* @interface ReplicationJob * @interface ReplicationJob
*/ */
export interface ReplicationJob { export interface ReplicationJob {
metadata?: Metadata; metadata?: Metadata;
data: ReplicationJobItem[]; data: ReplicationJobItem[];
} }
/** /**
* Interface for replication job item. * Interface for replication job item.
* *
* @export * @export
* @interface ReplicationJob * @interface ReplicationJob
*/ */
export interface ReplicationJobItem extends Base { export interface ReplicationJobItem extends Base {
[key: string]: any | any[]; [key: string]: any | any[];
status: string; status: string;
repository: string; repository: string;
policy_id: number; policy_id: number;
operation: string; operation: string;
tags: string; tags: string;
} }
/** /**
* Interface for storing metadata of response. * Interface for storing metadata of response.
* *
* @export * @export
* @interface Metadata * @interface Metadata
*/ */
export interface Metadata { export interface Metadata {
xTotalCount: number; xTotalCount: number;
} }
/** /**
* Interface for access log. * Interface for access log.
* *
* @export * @export
* @interface AccessLog * @interface AccessLog
*/ */
export interface AccessLog { export interface AccessLog {
metadata?: Metadata; metadata?: Metadata;
data: AccessLogItem[]; data: AccessLogItem[];
} }
/** /**
* The access log data. * The access log data.
* *
* @export * @export
* @interface AccessLogItem * @interface AccessLogItem
*/ */
export interface AccessLogItem { export interface AccessLogItem {
[key: string]: any | any[]; [key: string]: any | any[];
log_id: number; log_id: number;
project_id: number; project_id: number;
repo_name: string; repo_name: string;
repo_tag: string; repo_tag: string;
operation: string; operation: string;
op_time: string | Date; op_time: string | Date;
user_id: number; user_id: number;
username: string; username: string;
keywords?: string; //NOT used now keywords?: string; // NOT used now
guid?: string; //NOT used now guid?: string; // NOT used now
} }
/** /**
* Global system info. * Global system info.
* *
* @export * @export
* @interface SystemInfo * @interface SystemInfo
* *
*/ */
export interface SystemInfo { export interface SystemInfo {
with_clair?: boolean; with_clair?: boolean;
with_notary?: boolean; with_notary?: boolean;
with_admiral?: boolean; with_admiral?: boolean;
admiral_endpoint?: string; admiral_endpoint?: string;
auth_mode?: string; auth_mode?: string;
registry_url?: string; registry_url?: string;
project_creation_restriction?: string; project_creation_restriction?: string;
self_registration?: boolean; self_registration?: boolean;
has_ca_root?: boolean; has_ca_root?: boolean;
harbor_version?: string; harbor_version?: string;
clair_vulnerability_status?: ClairDBStatus; clair_vulnerability_status?: ClairDBStatus;
next_scan_all?: number; next_scan_all?: number;
} }
/** /**
* Clair database status info. * Clair database status info.
* *
* @export * @export
* @interface ClairDetail * @interface ClairDetail
*/ */
export interface ClairDetail { export interface ClairDetail {
namespace: string; namespace: string;
last_update: number; last_update: number;
} }
export interface ClairDBStatus { export interface ClairDBStatus {
overall_last_update: number; overall_last_update: number;
details: ClairDetail[]; details: ClairDetail[];
} }
export enum VulnerabilitySeverity { export enum VulnerabilitySeverity {
_SEVERITY, NONE, UNKNOWN, LOW, MEDIUM, HIGH _SEVERITY,
NONE,
UNKNOWN,
LOW,
MEDIUM,
HIGH
} }
export interface VulnerabilityBase { export interface VulnerabilityBase {
id: string; id: string;
severity: VulnerabilitySeverity; severity: VulnerabilitySeverity;
package: string; package: string;
version: string; version: string;
} }
export interface VulnerabilityItem extends VulnerabilityBase { export interface VulnerabilityItem extends VulnerabilityBase {
link: string; link: string;
fixedVersion: string; fixedVersion: string;
layer?: string; layer?: string;
description: string; description: string;
} }
export interface VulnerabilitySummary { export interface VulnerabilitySummary {
image_digest?: string; image_digest?: string;
scan_status: string; scan_status: string;
job_id?: number; job_id?: number;
severity: VulnerabilitySeverity; severity: VulnerabilitySeverity;
components: VulnerabilityComponents; components: VulnerabilityComponents;
update_time: Date; // Use as complete timestamp update_time: Date; // Use as complete timestamp
} }
export interface VulnerabilityComponents { export interface VulnerabilityComponents {
total: number; total: number;
summary: VulnerabilitySeverityMetrics[]; summary: VulnerabilitySeverityMetrics[];
} }
export interface VulnerabilitySeverityMetrics { export interface VulnerabilitySeverityMetrics {
severity: VulnerabilitySeverity; severity: VulnerabilitySeverity;
count: number; count: number;
} }
export interface TagClickEvent { export interface TagClickEvent {
project_id: string | number; project_id: string | number;
repository_name: string; repository_name: string;
tag_name: string; tag_name: string;
} }
export interface Label { export interface Label {
[key: string]: any | any[]; [key: string]: any | any[];
name: string; name: string;
description: string; description: string;
color: string; color: string;
scope: string; scope: string;
project_id: number; project_id: number;
} }
export interface CardItemEvent { export interface CardItemEvent {
event_type: string; event_type: string;
item: any; item: any;
additional_info?: any; additional_info?: any;
} }
export interface ScrollPosition { export interface ScrollPosition {
sH: number; sH: number;
sT: number; sT: number;
cH: number; cH: number;
}; }

View File

@ -1,84 +1,92 @@
import { Observable } from 'rxjs/Observable'; import { Observable } from "rxjs/Observable";
import { RequestQueryParams } from './RequestQueryParams';
import { ReplicationJob, ReplicationRule } from './interface';
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of'; import "rxjs/add/observable/of";
import { Http, RequestOptions } from '@angular/http'; import { Http } from "@angular/http";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
import { buildHttpRequestOptions, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from '../utils'; import { HTTP_GET_OPTIONS } from "../utils";
/** /**
* Define the service methods to handle the job log related things. * Define the service methods to handle the job log related things.
* *
* @export * @export
* @abstract * @abstract
* @class JobLogService * @class JobLogService
*/ */
export abstract class JobLogService { export abstract class JobLogService {
/**
/** * Get the log of the specified job
* Get the log of the specified job *
* * @abstract
* @abstract * @param {string} jobType
* @param {string} jobType * @param {(number | string)} jobId
* @param {(number | string)} jobId * @returns {(Observable<string> | Promise<string> | string)}
* @returns {(Observable<string> | Promise<string> | string)} * @memberof JobLogService
* @memberof JobLogService */
*/ abstract getJobLog(
abstract getJobLog(jobType: string, jobId: number | string): Observable<string> | Promise<string> | string; jobType: string,
jobId: number | string
): Observable<string> | Promise<string> | string;
} }
/** /**
* Implement default service for job log service. * Implement default service for job log service.
* *
* @export * @export
* @class JobLogDefaultService * @class JobLogDefaultService
* @extends {ReplicationService} * @extends {ReplicationService}
*/ */
@Injectable() @Injectable()
export class JobLogDefaultService extends JobLogService { export class JobLogDefaultService extends JobLogService {
_replicationJobBaseUrl: string; _replicationJobBaseUrl: string;
_scanningJobBaseUrl: string; _scanningJobBaseUrl: string;
_supportedJobTypes: string[]; _supportedJobTypes: string[];
constructor( constructor(
private http: Http, private http: Http,
@Inject(SERVICE_CONFIG) config: IServiceConfig @Inject(SERVICE_CONFIG) config: IServiceConfig
) { ) {
super(); super();
this._replicationJobBaseUrl = config.replicationJobEndpoint ? this._replicationJobBaseUrl = config.replicationJobEndpoint
config.replicationJobEndpoint : '/api/jobs/replication'; ? config.replicationJobEndpoint
this._scanningJobBaseUrl = config.scanJobEndpoint ? config.scanJobEndpoint : "/api/jobs/scan"; : "/api/jobs/replication";
this._supportedJobTypes = ["replication", "scan"]; this._scanningJobBaseUrl = config.scanJobEndpoint
? config.scanJobEndpoint
: "/api/jobs/scan";
this._supportedJobTypes = ["replication", "scan"];
}
_getJobLog(logUrl: string): Observable<string> | Promise<string> | string {
return this.http
.get(logUrl, HTTP_GET_OPTIONS)
.toPromise()
.then(response => response.text())
.catch(error => Promise.reject(error));
}
_isSupportedJobType(jobType: string): boolean {
if (this._supportedJobTypes.find((t: string) => t === jobType)) {
return true;
} }
_getJobLog(logUrl: string): Observable<string> | Promise<string> | string { return false;
return this.http.get(logUrl, HTTP_GET_OPTIONS).toPromise() }
.then(response => response.text())
.catch(error => Promise.reject(error)); public getJobLog(
jobType: string,
jobId: number | string
): Observable<string> | Promise<string> | string {
if (!this._isSupportedJobType(jobType)) {
return Promise.reject("Unsupport job type: " + jobType);
}
if (!jobId || jobId <= 0) {
return Promise.reject("Bad argument");
} }
_isSupportedJobType(jobType: string): boolean { let logUrl: string = `${this._replicationJobBaseUrl}/${jobId}/log`;
if (this._supportedJobTypes.find((t: string) => t === jobType)) { if (jobType === "scan") {
return true; logUrl = `${this._scanningJobBaseUrl}/${jobId}/log`;
}
return false;
} }
public getJobLog(jobType: string, jobId: number | string): Observable<string> | Promise<string> | string { return this._getJobLog(logUrl);
if (!this._isSupportedJobType(jobType)) { }
return Promise.reject("Unsupport job type: " + jobType); }
}
if (!jobId || jobId <= 0) {
return Promise.reject('Bad argument');
}
let logUrl: string = `${this._replicationJobBaseUrl}/${jobId}/log`;
if (jobType === "scan") {
logUrl = `${this._scanningJobBaseUrl}/${jobId}/log`;
}
return this._getJobLog(logUrl);
}
}

View File

@ -1,125 +1,170 @@
import {Observable} from "rxjs/Observable"; import { Observable } from "rxjs/Observable";
import {Label} from "./interface"; import { Label } from "./interface";
import {Inject, Injectable} from "@angular/core"; import { Inject, Injectable } from "@angular/core";
import {Http} from "@angular/http"; import { Http } from "@angular/http";
import {IServiceConfig, SERVICE_CONFIG} from "../service.config"; import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
import {buildHttpRequestOptions, HTTP_JSON_OPTIONS} from "../utils"; import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from "../utils";
import {RequestQueryParams} from "./RequestQueryParams"; import { RequestQueryParams } from "./RequestQueryParams";
export abstract class LabelService { export abstract class LabelService {
abstract getGLabels(name?: string, queryParams?: RequestQueryParams): Observable<Label[]> | Promise<Label[]>; abstract getGLabels(
name?: string,
queryParams?: RequestQueryParams
): Observable<Label[]> | Promise<Label[]>;
abstract getPLabels(projectId: number, name?: string, queryParams?: RequestQueryParams): Observable<Label[]> | Promise<Label[]>; abstract getPLabels(
projectId: number,
name?: string,
queryParams?: RequestQueryParams
): Observable<Label[]> | Promise<Label[]>;
abstract getLabels(scope: string, projectId: number, name?: string, queryParams?: RequestQueryParams): Observable<Label[]> | Promise<Label[]>; abstract getLabels(
scope: string,
projectId: number,
name?: string,
queryParams?: RequestQueryParams
): Observable<Label[]> | Promise<Label[]>;
abstract createLabel(label: Label): Observable<Label> | Promise<Label> | Label; abstract createLabel(
label: Label
): Observable<Label> | Promise<Label> | Label;
abstract getLabel(id: number): Observable<Label> | Promise<Label> | Label; abstract getLabel(id: number): Observable<Label> | Promise<Label> | Label;
abstract updateLabel(id: number, param: Label): Observable<any> | Promise<any> | any; abstract updateLabel(
id: number,
param: Label
): Observable<any> | Promise<any> | any;
abstract deleteLabel(id: number): Observable<any> | Promise<any> | any; abstract deleteLabel(id: number): Observable<any> | Promise<any> | any;
} }
@Injectable() @Injectable()
export class LabelDefaultService extends LabelService { export class LabelDefaultService extends LabelService {
_labelUrl: string; _labelUrl: string;
constructor( constructor(
@Inject(SERVICE_CONFIG) config: IServiceConfig, @Inject(SERVICE_CONFIG) config: IServiceConfig,
private http: Http private http: Http
) { ) {
super(); super();
this._labelUrl = config.labelEndpoint ? config.labelEndpoint : "/api/labels"; this._labelUrl = config.labelEndpoint
} ? config.labelEndpoint
: "/api/labels";
}
getLabels(scope: string, projectId: number, name?: string, queryParams?: RequestQueryParams): Observable<Label[]> | Promise<Label[]> { getLabels(
if (!queryParams) { scope: string,
queryParams = new RequestQueryParams(); projectId: number,
} name?: string,
if (scope) { queryParams?: RequestQueryParams
queryParams.set('scope', scope); ): Observable<Label[]> | Promise<Label[]> {
} if (!queryParams) {
if (projectId) { queryParams = new RequestQueryParams();
queryParams.set('project_id', '' + projectId);
}
if (name) {
queryParams.set('name', '' + name);
}
return this.http.get(this._labelUrl, buildHttpRequestOptions(queryParams)).toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
} }
if (scope) {
queryParams.set("scope", scope);
}
if (projectId) {
queryParams.set("project_id", "" + projectId);
}
if (name) {
queryParams.set("name", "" + name);
}
return this.http
.get(this._labelUrl, buildHttpRequestOptions(queryParams))
.toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
}
getGLabels(name?: string, queryParams?: RequestQueryParams): Observable<Label[]> | Promise<Label[]> { getGLabels(
if (!queryParams) { name?: string,
queryParams = new RequestQueryParams(); queryParams?: RequestQueryParams
} ): Observable<Label[]> | Promise<Label[]> {
queryParams.set('scope', 'g'); if (!queryParams) {
queryParams = new RequestQueryParams();
}
queryParams.set("scope", "g");
if (name) { if (name) {
queryParams.set('name', '' + name); queryParams.set("name", "" + name);
}
return this.http.get(this._labelUrl, buildHttpRequestOptions(queryParams)).toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
} }
return this.http
.get(this._labelUrl, buildHttpRequestOptions(queryParams))
.toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
}
getPLabels(projectId: number, name?: string, queryParams?: RequestQueryParams): Observable<Label[]> | Promise<Label[]> { getPLabels(
if (!queryParams) { projectId: number,
queryParams = new RequestQueryParams(); name?: string,
} queryParams?: RequestQueryParams
queryParams.set('scope', 'p'); ): Observable<Label[]> | Promise<Label[]> {
if (projectId) { if (!queryParams) {
queryParams.set('project_id', '' + projectId); queryParams = new RequestQueryParams();
}
if (name) {
queryParams.set('name', '' + name);
}
return this.http.get(this._labelUrl, buildHttpRequestOptions(queryParams)).toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
} }
queryParams.set("scope", "p");
if (projectId) {
queryParams.set("project_id", "" + projectId);
}
if (name) {
queryParams.set("name", "" + name);
}
return this.http
.get(this._labelUrl, buildHttpRequestOptions(queryParams))
.toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
}
createLabel(label: Label): Observable<any> | Promise<any> | any { createLabel(label: Label): Observable<any> | Promise<any> | any {
if (!label) { if (!label) {
return Promise.reject('Invalid label.'); return Promise.reject("Invalid label.");
}
return this.http.post(this._labelUrl, JSON.stringify(label), HTTP_JSON_OPTIONS).toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
} }
return this.http
.post(this._labelUrl, JSON.stringify(label), HTTP_JSON_OPTIONS)
.toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
getLabel(id: number): Observable<Label> | Promise<Label> | Label { getLabel(id: number): Observable<Label> | Promise<Label> | Label {
if (!id || id <= 0) { if (!id || id <= 0) {
return Promise.reject('Bad request argument.'); return Promise.reject("Bad request argument.");
}
let reqUrl = `${this._labelUrl}/${id}`
return this.http.get(reqUrl).toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
} }
let reqUrl = `${this._labelUrl}/${id}`;
return this.http
.get(reqUrl)
.toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
}
updateLabel(id: number, label: Label): Observable<any> | Promise<any> | any { updateLabel(id: number, label: Label): Observable<any> | Promise<any> | any {
if (!id || id <= 0) { if (!id || id <= 0) {
return Promise.reject('Bad request argument.'); return Promise.reject("Bad request argument.");
}
if (!label) {
return Promise.reject('Invalid endpoint.');
}
let reqUrl = `${this._labelUrl}/${id}`
return this.http.put(reqUrl, JSON.stringify(label), HTTP_JSON_OPTIONS).toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
} }
deleteLabel(id: number): Observable<any> | Promise<any> | any { if (!label) {
if (!id || id <= 0) { return Promise.reject("Invalid endpoint.");
return Promise.reject('Bad request argument.');
}
let reqUrl = `${this._labelUrl}/${id}`
return this.http.delete(reqUrl).toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
} }
} let reqUrl = `${this._labelUrl}/${id}`;
return this.http
.put(reqUrl, JSON.stringify(label), HTTP_JSON_OPTIONS)
.toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
deleteLabel(id: number): Observable<any> | Promise<any> | any {
if (!id || id <= 0) {
return Promise.reject("Bad request argument.");
}
let reqUrl = `${this._labelUrl}/${id}`;
return this.http
.delete(reqUrl)
.toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
}

View File

@ -1,13 +1,17 @@
import { Observable } from 'rxjs/Observable'; import { Observable } from "rxjs/Observable";
import { Injectable, Inject } from '@angular/core'; import { Injectable, Inject } from "@angular/core";
import { Http, Headers, RequestOptions } from '@angular/http'; import { Http } from "@angular/http";
import 'rxjs/add/observable/of'; import "rxjs/add/observable/of";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
import { Project } from '../project-policy-config/project'; import { Project } from "../project-policy-config/project";
import { ProjectPolicy } from '../project-policy-config/project-policy-config.component'; import { ProjectPolicy } from "../project-policy-config/project-policy-config.component";
import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS, buildHttpRequestOptions} from "../utils"; import {
import {RequestQueryParams} from "./RequestQueryParams"; HTTP_JSON_OPTIONS,
HTTP_GET_OPTIONS,
buildHttpRequestOptions
} from "../utils";
import { RequestQueryParams } from "./RequestQueryParams";
/** /**
* Define the service methods to handle the Prject related things. * Define the service methods to handle the Prject related things.
@ -18,41 +22,51 @@ import {RequestQueryParams} from "./RequestQueryParams";
*/ */
export abstract class ProjectService { export abstract class ProjectService {
/** /**
* Get Infomations of a specific Project. * Get Infomations of a specific Project.
* *
* @abstract * @abstract
* @param {string|number} [projectId] * @param {string|number} [projectId]
* @returns {(Observable<Project> | Promise<Project> | Project)} * @returns {(Observable<Project> | Promise<Project> | Project)}
* *
* @memberOf ProjectService * @memberOf ProjectService
*/ */
abstract getProject(projectId: number | string): Observable<Project> | Promise<Project> | Project; abstract getProject(
projectId: number | string
): Observable<Project> | Promise<Project> | Project;
/** /**
* Update the specified project. * Update the specified project.
* *
* @abstract * @abstract
* @param {(number | string)} projectId * @param {(number | string)} projectId
* @param {ProjectPolicy} projectPolicy * @param {ProjectPolicy} projectPolicy
* @returns {(Observable<any> | Promise<any> | any)} * @returns {(Observable<any> | Promise<any> | any)}
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract updateProjectPolicy(projectId: number | string, projectPolicy: ProjectPolicy): Observable<any> | Promise<any> | any; abstract updateProjectPolicy(
projectId: number | string,
projectPolicy: ProjectPolicy
): Observable<any> | Promise<any> | any;
/** /**
* Get all projects * Get all projects
* *
* @abstract * @abstract
* @param {string} name * @param {string} name
* @param {number} isPublic * @param {number} isPublic
* @param {number} page * @param {number} page
* @param {number} pageSize * @param {number} pageSize
* @returns {(Observable<any> | Promise<any> | any)} * @returns {(Observable<any> | Promise<any> | any)}
* *
* @memberOf EndpointService * @memberOf EndpointService
*/ */
abstract listProjects(name: string, isPublic: number, page?: number, pageSize?: number): Observable<Project[]> | Promise<Project[]> | Project[]; abstract listProjects(
name: string,
isPublic: number,
page?: number,
pageSize?: number
): Observable<Project[]> | Promise<Project[]> | Project[];
} }
/** /**
@ -64,61 +78,78 @@ export abstract class ProjectService {
*/ */
@Injectable() @Injectable()
export class ProjectDefaultService extends ProjectService { export class ProjectDefaultService extends ProjectService {
constructor( constructor(
private http: Http, private http: Http,
@Inject(SERVICE_CONFIG) private config: IServiceConfig @Inject(SERVICE_CONFIG) private config: IServiceConfig
) { ) {
super(); super();
} }
public getProject(projectId: number | string): Observable<Project> | Promise<Project> | Project { public getProject(
projectId: number | string
): Observable<Project> | Promise<Project> | Project {
if (!projectId) { if (!projectId) {
return Promise.reject('Bad argument'); return Promise.reject("Bad argument");
} }
let baseUrl: string = this.config.projectBaseEndpoint ? this.config.projectBaseEndpoint : '/api/projects'; let baseUrl: string = this.config.projectBaseEndpoint
? this.config.projectBaseEndpoint
: "/api/projects";
return this.http return this.http
.get(`${baseUrl}/${projectId}`, HTTP_GET_OPTIONS) .get(`${baseUrl}/${projectId}`, HTTP_GET_OPTIONS)
.map(response => response.json()) .map(response => response.json())
.catch(error => Observable.throw(error)); .catch(error => Observable.throw(error));
} }
public listProjects(name: string, isPublic: number, page?: number, pageSize?: number): Observable<Project[]> | Promise<Project[]> | Project[] { public listProjects(
let baseUrl: string = this.config.projectBaseEndpoint ? this.config.projectBaseEndpoint : '/api/projects'; name: string,
let params = new RequestQueryParams(); isPublic: number,
if (page && pageSize) { page?: number,
params.set('page', page + ''); pageSize?: number
params.set('page_size', pageSize + ''); ): Observable<Project[]> | Promise<Project[]> | Project[] {
} let baseUrl: string = this.config.projectBaseEndpoint
if (name && name.trim() !== "") { ? this.config.projectBaseEndpoint
params.set('name', name); : "/api/projects";
let params = new RequestQueryParams();
} if (page && pageSize) {
if (isPublic !== undefined) { params.set("page", page + "");
params.set('public', '' + isPublic); params.set("page_size", pageSize + "");
} }
if (name && name.trim() !== "") {
// let options = new RequestOptions({ headers: this.getHeaders, search: params }); params.set("name", name);
return this.http }
.get(baseUrl, buildHttpRequestOptions(params)) if (isPublic !== undefined) {
.map(response => response.json()) params.set("public", "" + isPublic);
.catch(error => Observable.throw(error));
} }
public updateProjectPolicy(projectId: number | string, projectPolicy: ProjectPolicy): any { // let options = new RequestOptions({ headers: this.getHeaders, search: params });
let baseUrl: string = this.config.projectBaseEndpoint ? this.config.projectBaseEndpoint : '/api/projects';
return this.http return this.http
.put(`${baseUrl}/${projectId}`, { .get(baseUrl, buildHttpRequestOptions(params))
'metadata': { .map(response => response.json())
'public': projectPolicy.Public ? 'true' : 'false', .catch(error => Observable.throw(error));
'enable_content_trust': projectPolicy.ContentTrust ? 'true' : 'false', }
'prevent_vul': projectPolicy.PreventVulImg ? 'true' : 'false',
'severity': projectPolicy.PreventVulImgSeverity, public updateProjectPolicy(
'auto_scan': projectPolicy.ScanImgOnPush ? 'true' : 'false' projectId: number | string,
} projectPolicy: ProjectPolicy
}, ): any {
HTTP_JSON_OPTIONS) let baseUrl: string = this.config.projectBaseEndpoint
.map(response => response.status) ? this.config.projectBaseEndpoint
.catch(error => Observable.throw(error)); : "/api/projects";
return this.http
.put(
`${baseUrl}/${projectId}`,
{
metadata: {
public: projectPolicy.Public ? "true" : "false",
enable_content_trust: projectPolicy.ContentTrust ? "true" : "false",
prevent_vul: projectPolicy.PreventVulImg ? "true" : "false",
severity: projectPolicy.PreventVulImgSeverity,
auto_scan: projectPolicy.ScanImgOnPush ? "true" : "false"
}
},
HTTP_JSON_OPTIONS
)
.map(response => response.status)
.catch(error => Observable.throw(error));
} }
} }

View File

@ -1,310 +1,420 @@
import { Observable } from 'rxjs/Observable'; import { Http } from "@angular/http";
import { RequestQueryParams } from './RequestQueryParams';
import {ReplicationJob, ReplicationRule, ReplicationJobItem} from './interface';
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of'; import { Observable } from "rxjs/Observable";
import { Http, RequestOptions } from '@angular/http'; import "rxjs/add/observable/of";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { buildHttpRequestOptions, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from '../utils'; import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
import {
buildHttpRequestOptions,
HTTP_JSON_OPTIONS,
HTTP_GET_OPTIONS
} from "../utils";
import {
ReplicationJob,
ReplicationRule,
ReplicationJobItem
} from "./interface";
import { RequestQueryParams } from "./RequestQueryParams";
/** /**
* Define the service methods to handle the replication (rule and job) related things. * Define the service methods to handle the replication (rule and job) related things.
* *
* @export * @export
* @abstract * @abstract
* @class ReplicationService * @class ReplicationService
*/ */
export abstract class ReplicationService { export abstract class ReplicationService {
/** /**
* Get the replication rules. * Get the replication rules.
* Set the argument 'projectId' to limit the data scope to the specified project; * Set the argument 'projectId' to limit the data scope to the specified project;
* set the argument 'ruleName' to return the rule only match the name pattern; * set the argument 'ruleName' to return the rule only match the name pattern;
* if pagination needed, use the queryParams to add query parameters. * if pagination needed, use the queryParams to add query parameters.
* *
* @abstract * @abstract
* @param {(number | string)} [projectId] * @param {(number | string)} [projectId]
* @param {string} [ruleName] * @param {string} [ruleName]
* @param {RequestQueryParams} [queryParams] * @param {RequestQueryParams} [queryParams]
* @returns {(Observable<ReplicationRule[]> | Promise<ReplicationRule[]> | ReplicationRule[])} * @returns {(Observable<ReplicationRule[]> | Promise<ReplicationRule[]> | ReplicationRule[])}
* *
* @memberOf ReplicationService * @memberOf ReplicationService
*/ */
abstract getReplicationRules(projectId?: number | string, ruleName?: string, queryParams?: RequestQueryParams): Observable<ReplicationRule[]> | Promise<ReplicationRule[]> | ReplicationRule[]; abstract getReplicationRules(
projectId?: number | string,
ruleName?: string,
queryParams?: RequestQueryParams
):
| Observable<ReplicationRule[]>
| Promise<ReplicationRule[]>
| ReplicationRule[];
/** /**
* Get the specified replication rule. * Get the specified replication rule.
* *
* @abstract * @abstract
* @param {(number | string)} ruleId * @param {(number | string)} ruleId
* @returns {(Observable<ReplicationRule> | Promise<ReplicationRule> | ReplicationRule)} * @returns {(Observable<ReplicationRule> | Promise<ReplicationRule> | ReplicationRule)}
* *
* @memberOf ReplicationService * @memberOf ReplicationService
*/ */
abstract getReplicationRule(ruleId: number | string): Observable<ReplicationRule> | Promise<ReplicationRule> | ReplicationRule; abstract getReplicationRule(
ruleId: number | string
): Observable<ReplicationRule> | Promise<ReplicationRule> | ReplicationRule;
/** /**
* Create new replication rule. * Create new replication rule.
* *
* @abstract * @abstract
* @param {ReplicationRule} replicationRule * @param {ReplicationRule} replicationRule
* @returns {(Observable<any> | Promise<any> | any)} * @returns {(Observable<any> | Promise<any> | any)}
* *
* @memberOf ReplicationService * @memberOf ReplicationService
*/ */
abstract createReplicationRule(replicationRule: ReplicationRule): Observable<any> | Promise<any> | any; abstract createReplicationRule(
replicationRule: ReplicationRule
): Observable<any> | Promise<any> | any;
/** /**
* Update the specified replication rule. * Update the specified replication rule.
* *
* @abstract * @abstract
* @param {ReplicationRule} replicationRule * @param {ReplicationRule} replicationRule
* @returns {(Observable<any> | Promise<any> | any)} * @returns {(Observable<any> | Promise<any> | any)}
* *
* @memberOf ReplicationService * @memberOf ReplicationService
*/ */
abstract updateReplicationRule(id: number, rep: ReplicationRule): Observable<any> | Promise<any> | any; abstract updateReplicationRule(
id: number,
rep: ReplicationRule
): Observable<any> | Promise<any> | any;
/** /**
* Delete the specified replication rule. * Delete the specified replication rule.
* *
* @abstract * @abstract
* @param {(number | string)} ruleId * @param {(number | string)} ruleId
* @returns {(Observable<any> | Promise<any> | any)} * @returns {(Observable<any> | Promise<any> | any)}
* *
* @memberOf ReplicationService * @memberOf ReplicationService
*/ */
abstract deleteReplicationRule(ruleId: number | string): Observable<any> | Promise<any> | any; abstract deleteReplicationRule(
ruleId: number | string
): Observable<any> | Promise<any> | any;
/** /**
* Enable the specified replication rule. * Enable the specified replication rule.
* *
* @abstract * @abstract
* @param {(number | string)} ruleId * @param {(number | string)} ruleId
* @returns {(Observable<any> | Promise<any> | any)} * @returns {(Observable<any> | Promise<any> | any)}
* *
* @memberOf ReplicationService * @memberOf ReplicationService
*/ */
abstract enableReplicationRule(ruleId: number | string, enablement: number): Observable<any> | Promise<any> | any; abstract enableReplicationRule(
ruleId: number | string,
enablement: number
): Observable<any> | Promise<any> | any;
/** /**
* Disable the specified replication rule. * Disable the specified replication rule.
* *
* @abstract * @abstract
* @param {(number | string)} ruleId * @param {(number | string)} ruleId
* @returns {(Observable<any> | Promise<any> | any)} * @returns {(Observable<any> | Promise<any> | any)}
* *
* @memberOf ReplicationService * @memberOf ReplicationService
*/ */
abstract disableReplicationRule(ruleId: number | string): Observable<any> | Promise<any> | any; abstract disableReplicationRule(
ruleId: number | string
): Observable<any> | Promise<any> | any;
abstract replicateRule(
ruleId: number | string
): Observable<any> | Promise<any> | any;
abstract replicateRule(ruleId: number | string): Observable<any> | Promise<any> | any; /**
* Get the jobs for the specified replication rule.
* Set query parameters through 'queryParams', support:
* - status
* - repository
* - startTime and endTime
* - page
* - pageSize
*
* @abstract
* @param {(number | string)} ruleId
* @param {RequestQueryParams} [queryParams]
* @returns {(Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob)}
*
* @memberOf ReplicationService
*/
abstract getJobs(
ruleId: number | string,
queryParams?: RequestQueryParams
): Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob;
/** /**
* Get the jobs for the specified replication rule. * Get the log of the specified job.
* Set query parameters through 'queryParams', support: *
* - status * @abstract
* - repository * @param {(number | string)} jobId
* - startTime and endTime * @returns {(Observable<string> | Promise<string> | string)}
* - page * @memberof ReplicationService
* - pageSize */
* abstract getJobLog(
* @abstract jobId: number | string
* @param {(number | string)} ruleId ): Observable<string> | Promise<string> | string;
* @param {RequestQueryParams} [queryParams]
* @returns {(Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob)}
*
* @memberOf ReplicationService
*/
abstract getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob;
/** abstract stopJobs(
* Get the log of the specified job. jobId: number | string
* ): Observable<string> | Promise<string> | string;
* @abstract
* @param {(number | string)} jobId
* @returns {(Observable<string> | Promise<string> | string)}
* @memberof ReplicationService
*/
abstract getJobLog(jobId: number | string): Observable<string> | Promise<string> | string;
abstract stopJobs(jobId: number | string): Observable<string> | Promise<string> | string;
} }
/** /**
* Implement default service for replication rule and job. * Implement default service for replication rule and job.
* *
* @export * @export
* @class ReplicationDefaultService * @class ReplicationDefaultService
* @extends {ReplicationService} * @extends {ReplicationService}
*/ */
@Injectable() @Injectable()
export class ReplicationDefaultService extends ReplicationService { export class ReplicationDefaultService extends ReplicationService {
_ruleBaseUrl: string; _ruleBaseUrl: string;
_jobBaseUrl: string; _jobBaseUrl: string;
_replicateUrl: string; _replicateUrl: string;
constructor( constructor(
private http: Http, private http: Http,
@Inject(SERVICE_CONFIG) config: IServiceConfig @Inject(SERVICE_CONFIG) config: IServiceConfig
) { ) {
super(); super();
this._ruleBaseUrl = config.replicationRuleEndpoint ? config.replicationRuleEndpoint : '/api/policies/replication'; this._ruleBaseUrl = config.replicationRuleEndpoint
this._jobBaseUrl = config.replicationJobEndpoint ? config.replicationJobEndpoint : '/api/jobs/replication'; ? config.replicationRuleEndpoint
this._replicateUrl = config.replicationBaseEndpoint ? config.replicationBaseEndpoint : '/api/replications'; : "/api/policies/replication";
this._jobBaseUrl = config.replicationJobEndpoint
? config.replicationJobEndpoint
: "/api/jobs/replication";
this._replicateUrl = config.replicationBaseEndpoint
? config.replicationBaseEndpoint
: "/api/replications";
}
// Private methods
// Check if the rule object is valid
_isValidRule(rule: ReplicationRule): boolean {
return (
rule !== undefined &&
rule != null &&
rule.name !== undefined &&
rule.name.trim() !== "" &&
rule.targets.length !== 0
);
}
public getReplicationRules(
projectId?: number | string,
ruleName?: string,
queryParams?: RequestQueryParams
):
| Observable<ReplicationRule[]>
| Promise<ReplicationRule[]>
| ReplicationRule[] {
if (!queryParams) {
queryParams = new RequestQueryParams();
} }
//Private methods if (projectId) {
//Check if the rule object is valid queryParams.set("project_id", "" + projectId);
_isValidRule(rule: ReplicationRule): boolean {
return rule !== undefined && rule != null && rule.name !== undefined && rule.name.trim() !== '' && rule.targets.length !== 0;
} }
public getReplicationRules(projectId?: number | string, ruleName?: string, queryParams?: RequestQueryParams): Observable<ReplicationRule[]> | Promise<ReplicationRule[]> | ReplicationRule[] { if (ruleName) {
if (!queryParams) { queryParams.set("name", ruleName);
queryParams = new RequestQueryParams();
}
if (projectId) {
queryParams.set('project_id', '' + projectId);
}
if (ruleName) {
queryParams.set('name', ruleName);
}
return this.http.get(this._ruleBaseUrl, buildHttpRequestOptions(queryParams)).toPromise()
.then(response => response.json() as ReplicationRule[])
.catch(error => Promise.reject(error));
} }
public getReplicationRule(ruleId: number | string): Observable<ReplicationRule> | Promise<ReplicationRule> | ReplicationRule { return this.http
if (!ruleId) { .get(this._ruleBaseUrl, buildHttpRequestOptions(queryParams))
return Promise.reject("Bad argument"); .toPromise()
} .then(response => response.json() as ReplicationRule[])
.catch(error => Promise.reject(error));
}
let url: string = `${this._ruleBaseUrl}/${ruleId}`; public getReplicationRule(
return this.http.get(url, HTTP_GET_OPTIONS).toPromise() ruleId: number | string
.then(response => response.json() as ReplicationRule) ): Observable<ReplicationRule> | Promise<ReplicationRule> | ReplicationRule {
.catch(error => Promise.reject(error)); if (!ruleId) {
return Promise.reject("Bad argument");
} }
public createReplicationRule(replicationRule: ReplicationRule): Observable<any> | Promise<any> | any { let url: string = `${this._ruleBaseUrl}/${ruleId}`;
if (!this._isValidRule(replicationRule)) { return this.http
return Promise.reject('Bad argument'); .get(url, HTTP_GET_OPTIONS)
} .toPromise()
.then(response => response.json() as ReplicationRule)
.catch(error => Promise.reject(error));
}
return this.http.post(this._ruleBaseUrl, JSON.stringify(replicationRule), HTTP_JSON_OPTIONS).toPromise() public createReplicationRule(
.then(response => response) replicationRule: ReplicationRule
.catch(error => Promise.reject(error)); ): Observable<any> | Promise<any> | any {
if (!this._isValidRule(replicationRule)) {
return Promise.reject("Bad argument");
} }
public updateReplicationRule(id: number, rep: ReplicationRule): Observable<any> | Promise<any> | any { return this.http
if (!this._isValidRule(rep)) { .post(
return Promise.reject('Bad argument'); this._ruleBaseUrl,
} JSON.stringify(replicationRule),
HTTP_JSON_OPTIONS
)
.toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
let url = `${this._ruleBaseUrl}/${id}`; public updateReplicationRule(
return this.http.put(url, JSON.stringify(rep), HTTP_JSON_OPTIONS).toPromise() id: number,
.then(response => response) rep: ReplicationRule
.catch(error => Promise.reject(error)); ): Observable<any> | Promise<any> | any {
if (!this._isValidRule(rep)) {
return Promise.reject("Bad argument");
} }
public deleteReplicationRule(ruleId: number | string): Observable<any> | Promise<any> | any { let url = `${this._ruleBaseUrl}/${id}`;
if (!ruleId || ruleId <= 0) { return this.http
return Promise.reject('Bad argument'); .put(url, JSON.stringify(rep), HTTP_JSON_OPTIONS)
} .toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
let url: string = `${this._ruleBaseUrl}/${ruleId}`; public deleteReplicationRule(
return this.http.delete(url, HTTP_JSON_OPTIONS).toPromise() ruleId: number | string
.then(response => response) ): Observable<any> | Promise<any> | any {
.catch(error => Promise.reject(error)); if (!ruleId || ruleId <= 0) {
return Promise.reject("Bad argument");
} }
public replicateRule(ruleId: number | string): Observable<any> | Promise<any> | any { let url: string = `${this._ruleBaseUrl}/${ruleId}`;
if (!ruleId) { return this.http
return Promise.reject("Bad argument"); .delete(url, HTTP_JSON_OPTIONS)
} .toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
let url: string = `${this._replicateUrl}`; public replicateRule(
return this.http.post(url, {policy_id: ruleId}, HTTP_JSON_OPTIONS).toPromise() ruleId: number | string
.then(response => response) ): Observable<any> | Promise<any> | any {
.catch(error => Promise.reject(error)); if (!ruleId) {
return Promise.reject("Bad argument");
} }
public enableReplicationRule(ruleId: number | string, enablement: number): Observable<any> | Promise<any> | any { let url: string = `${this._replicateUrl}`;
if (!ruleId || ruleId <= 0) { return this.http
return Promise.reject('Bad argument'); .post(url, { policy_id: ruleId }, HTTP_JSON_OPTIONS)
} .toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
let url: string = `${this._ruleBaseUrl}/${ruleId}/enablement`; public enableReplicationRule(
return this.http.put(url, { enabled: enablement }, HTTP_JSON_OPTIONS).toPromise() ruleId: number | string,
.then(response => response) enablement: number
.catch(error => Promise.reject(error)); ): Observable<any> | Promise<any> | any {
if (!ruleId || ruleId <= 0) {
return Promise.reject("Bad argument");
} }
public disableReplicationRule(ruleId: number | string): Observable<any> | Promise<any> | any { let url: string = `${this._ruleBaseUrl}/${ruleId}/enablement`;
if (!ruleId || ruleId <= 0) { return this.http
return Promise.reject('Bad argument'); .put(url, { enabled: enablement }, HTTP_JSON_OPTIONS)
} .toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
let url: string = `${this._ruleBaseUrl}/${ruleId}/enablement`; public disableReplicationRule(
return this.http.put(url, { enabled: 0 }, HTTP_JSON_OPTIONS).toPromise() ruleId: number | string
.then(response => response) ): Observable<any> | Promise<any> | any {
.catch(error => Promise.reject(error)); if (!ruleId || ruleId <= 0) {
return Promise.reject("Bad argument");
} }
public getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob { let url: string = `${this._ruleBaseUrl}/${ruleId}/enablement`;
if (!ruleId || ruleId <= 0) { return this.http
return Promise.reject('Bad argument'); .put(url, { enabled: 0 }, HTTP_JSON_OPTIONS)
} .toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
if (!queryParams) { public getJobs(
queryParams = new RequestQueryParams(); ruleId: number | string,
} queryParams?: RequestQueryParams
): Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob {
queryParams.set('policy_id', '' + ruleId); if (!ruleId || ruleId <= 0) {
return this.http.get(this._jobBaseUrl, buildHttpRequestOptions(queryParams)).toPromise() return Promise.reject("Bad argument");
.then(response => {
let result: ReplicationJob = {
metadata: {
xTotalCount: 0
},
data: []
};
if (response && response.headers) {
let xHeader: string = response.headers.get("X-Total-Count");
if (xHeader) {
result.metadata.xTotalCount = parseInt(xHeader, 0);
}
}
result.data = response.json() as ReplicationJobItem[];
if (result.metadata.xTotalCount === 0) {
if (result.data && result.data.length > 0) {
result.metadata.xTotalCount = result.data.length;
}
}
return result;
})
.catch(error => Promise.reject(error));
} }
public getJobLog(jobId: number | string): Observable<string> | Promise<string> | string { if (!queryParams) {
if (!jobId || jobId <= 0) { queryParams = new RequestQueryParams();
return Promise.reject('Bad argument'); }
queryParams.set("policy_id", "" + ruleId);
return this.http
.get(this._jobBaseUrl, buildHttpRequestOptions(queryParams))
.toPromise()
.then(response => {
let result: ReplicationJob = {
metadata: {
xTotalCount: 0
},
data: []
};
if (response && response.headers) {
let xHeader: string = response.headers.get("X-Total-Count");
if (xHeader) {
result.metadata.xTotalCount = parseInt(xHeader, 0);
}
}
result.data = response.json() as ReplicationJobItem[];
if (result.metadata.xTotalCount === 0) {
if (result.data && result.data.length > 0) {
result.metadata.xTotalCount = result.data.length;
}
} }
let logUrl = `${this._jobBaseUrl}/${jobId}/log`; return result;
return this.http.get(logUrl, HTTP_GET_OPTIONS).toPromise() })
.then(response => response.text()) .catch(error => Promise.reject(error));
.catch(error => Promise.reject(error)); }
public getJobLog(
jobId: number | string
): Observable<string> | Promise<string> | string {
if (!jobId || jobId <= 0) {
return Promise.reject("Bad argument");
} }
public stopJobs(jobId: number | string): Observable<any> | Promise<any> | any { let logUrl = `${this._jobBaseUrl}/${jobId}/log`;
return this.http.put(this._jobBaseUrl, JSON.stringify({'policy_id': jobId, 'status': 'stop' }), HTTP_JSON_OPTIONS).toPromise() return this.http
.then(response => response) .get(logUrl, HTTP_GET_OPTIONS)
.catch(error => Promise.reject(error)); .toPromise()
} .then(response => response.text())
} .catch(error => Promise.reject(error));
}
public stopJobs(
jobId: number | string
): Observable<any> | Promise<any> | any {
return this.http
.put(
this._jobBaseUrl,
JSON.stringify({ policy_id: jobId, status: "stop" }),
HTTP_JSON_OPTIONS
)
.toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
}

View File

@ -1,114 +1,161 @@
import { Observable } from 'rxjs/Observable'; import { Http } from "@angular/http";
import 'rxjs/add/observable/of';
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { Observable } from "rxjs/Observable";
import { Http, URLSearchParams } from '@angular/http'; import "rxjs/add/observable/of";
import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from '../utils';
import { RequestQueryParams } from './RequestQueryParams';
import { import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
VulnerabilityItem, import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from "../utils";
VulnerabilitySummary import { RequestQueryParams } from "./RequestQueryParams";
} from './interface'; import { VulnerabilityItem, VulnerabilitySummary } from "./interface";
/** /**
* Get the vulnerabilities scanning results for the specified tag. * Get the vulnerabilities scanning results for the specified tag.
* *
* @export * @export
* @abstract * @abstract
* @class ScanningResultService * @class ScanningResultService
*/ */
export abstract class ScanningResultService { export abstract class ScanningResultService {
/** /**
* Get the summary of vulnerability scanning result. * Get the summary of vulnerability scanning result.
* *
* @abstract * @abstract
* @param {string} tagId * @param {string} tagId
* @returns {(Observable<VulnerabilitySummary> | Promise<VulnerabilitySummary> | VulnerabilitySummary)} * @returns {(Observable<VulnerabilitySummary> | Promise<VulnerabilitySummary> | VulnerabilitySummary)}
* *
* @memberOf ScanningResultService * @memberOf ScanningResultService
*/ */
abstract getVulnerabilityScanningSummary(repoName: string, tagId: string, queryParams?: RequestQueryParams): Observable<VulnerabilitySummary> | Promise<VulnerabilitySummary> | VulnerabilitySummary; abstract getVulnerabilityScanningSummary(
repoName: string,
tagId: string,
queryParams?: RequestQueryParams
):
| Observable<VulnerabilitySummary>
| Promise<VulnerabilitySummary>
| VulnerabilitySummary;
/** /**
* Get the detailed vulnerabilities scanning results. * Get the detailed vulnerabilities scanning results.
* *
* @abstract * @abstract
* @param {string} tagId * @param {string} tagId
* @returns {(Observable<VulnerabilityItem[]> | Promise<VulnerabilityItem[]> | VulnerabilityItem[])} * @returns {(Observable<VulnerabilityItem[]> | Promise<VulnerabilityItem[]> | VulnerabilityItem[])}
* *
* @memberOf ScanningResultService * @memberOf ScanningResultService
*/ */
abstract getVulnerabilityScanningResults(repoName: string, tagId: string, queryParams?: RequestQueryParams): Observable<VulnerabilityItem[]> | Promise<VulnerabilityItem[]> | VulnerabilityItem[]; abstract getVulnerabilityScanningResults(
repoName: string,
tagId: string,
queryParams?: RequestQueryParams
):
| Observable<VulnerabilityItem[]>
| Promise<VulnerabilityItem[]>
| VulnerabilityItem[];
/**
* Start a new vulnerability scanning
*
* @abstract
* @param {string} repoName
* @param {string} tagId
* @returns {(Observable<any> | Promise<any> | any)}
*
* @memberOf ScanningResultService
*/
abstract startVulnerabilityScanning(
repoName: string,
tagId: string
): Observable<any> | Promise<any> | any;
/** /**
* Start a new vulnerability scanning * Trigger the scanning all action.
* *
* @abstract * @abstract
* @param {string} repoName * @returns {(Observable<any> | Promise<any> | any)}
* @param {string} tagId *
* @returns {(Observable<any> | Promise<any> | any)} * @memberOf ScanningResultService
* */
* @memberOf ScanningResultService abstract startScanningAll(): Observable<any> | Promise<any> | any;
*/
abstract startVulnerabilityScanning(repoName: string, tagId: string): Observable<any> | Promise<any> | any;
/**
* Trigger the scanning all action.
*
* @abstract
* @returns {(Observable<any> | Promise<any> | any)}
*
* @memberOf ScanningResultService
*/
abstract startScanningAll(): Observable<any> | Promise<any> | any;
} }
@Injectable() @Injectable()
export class ScanningResultDefaultService extends ScanningResultService { export class ScanningResultDefaultService extends ScanningResultService {
_baseUrl: string = '/api/repositories'; _baseUrl: string = "/api/repositories";
constructor( constructor(
private http: Http, private http: Http,
@Inject(SERVICE_CONFIG) private config: IServiceConfig) { @Inject(SERVICE_CONFIG) private config: IServiceConfig
super(); ) {
if (this.config && this.config.vulnerabilityScanningBaseEndpoint) { super();
this._baseUrl = this.config.vulnerabilityScanningBaseEndpoint; if (this.config && this.config.vulnerabilityScanningBaseEndpoint) {
} this._baseUrl = this.config.vulnerabilityScanningBaseEndpoint;
}
}
getVulnerabilityScanningSummary(
repoName: string,
tagId: string,
queryParams?: RequestQueryParams
):
| Observable<VulnerabilitySummary>
| Promise<VulnerabilitySummary>
| VulnerabilitySummary {
if (!repoName || repoName.trim() === "" || !tagId || tagId.trim() === "") {
return Promise.reject("Bad argument");
} }
getVulnerabilityScanningSummary(repoName: string, tagId: string, queryParams?: RequestQueryParams): Observable<VulnerabilitySummary> | Promise<VulnerabilitySummary> | VulnerabilitySummary { return Observable.of({} as VulnerabilitySummary);
if (!repoName || repoName.trim() === '' || !tagId || tagId.trim() === '') { }
return Promise.reject('Bad argument');
}
return Observable.of({}); getVulnerabilityScanningResults(
repoName: string,
tagId: string,
queryParams?: RequestQueryParams
):
| Observable<VulnerabilityItem[]>
| Promise<VulnerabilityItem[]>
| VulnerabilityItem[] {
if (!repoName || repoName.trim() === "" || !tagId || tagId.trim() === "") {
return Promise.reject("Bad argument");
} }
getVulnerabilityScanningResults(repoName: string, tagId: string, queryParams?: RequestQueryParams): Observable<VulnerabilityItem[]> | Promise<VulnerabilityItem[]> | VulnerabilityItem[] { return this.http
if (!repoName || repoName.trim() === '' || !tagId || tagId.trim() === '') { .get(
return Promise.reject('Bad argument'); `${this._baseUrl}/${repoName}/tags/${tagId}/vulnerability/details`,
} buildHttpRequestOptions(queryParams)
)
.toPromise()
.then(response => response.json() as VulnerabilityItem[])
.catch(error => Promise.reject(error));
}
return this.http.get(`${this._baseUrl}/${repoName}/tags/${tagId}/vulnerability/details`, buildHttpRequestOptions(queryParams)).toPromise() startVulnerabilityScanning(
.then(response => response.json() as VulnerabilityItem[]) repoName: string,
.catch(error => Promise.reject(error)); tagId: string
): Observable<any> | Promise<any> | any {
if (!repoName || repoName.trim() === "" || !tagId || tagId.trim() === "") {
return Promise.reject("Bad argument");
} }
startVulnerabilityScanning(repoName: string, tagId: string): Observable<any> | Promise<any> | any { return this.http
if (!repoName || repoName.trim() === '' || !tagId || tagId.trim() === '') { .post(
return Promise.reject('Bad argument'); `${this._baseUrl}/${repoName}/tags/${tagId}/scan`,
} HTTP_JSON_OPTIONS
)
.toPromise()
.then(() => {
return true;
})
.catch(error => Promise.reject(error));
}
return this.http.post(`${this._baseUrl}/${repoName}/tags/${tagId}/scan`, HTTP_JSON_OPTIONS).toPromise() startScanningAll(): Observable<any> | Promise<any> | any {
.then(() => { return true }) return this.http
.catch(error => Promise.reject(error)); .post(`${this._baseUrl}/scanAll`, HTTP_JSON_OPTIONS)
} .toPromise()
.then(() => {
startScanningAll(): Observable<any> | Promise<any> | any { return true;
return this.http.post(`${this._baseUrl}/scanAll`, HTTP_JSON_OPTIONS).toPromise() })
.then(() => {return true}) .catch(error => Promise.reject(error));
.catch(error => Promise.reject(error)); }
} }
}

View File

@ -1,29 +1,25 @@
import { TestBed, inject, async } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { TagService, TagDefaultService } from './tag.service';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { TagService, TagDefaultService } from './tag.service';
import { Tag } from './interface';
import { VerifiedSignature } from './tag.service';
import { toPromise } from '../utils';
describe('TagService', () => { describe('TagService', () => {
let mockTags: Tag[] = [ // let mockTags: Tag[] = [
{ // {
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", // "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
"name": "1.11.5", // "name": "1.11.5",
"size": "2049", // "size": "2049",
"architecture": "amd64", // "architecture": "amd64",
"os": "linux", // "os": "linux",
"docker_version": "1.12.3", // "docker_version": "1.12.3",
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"", // "author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
"created": new Date("2016-11-08T22:41:15.912313785Z"), // "created": new Date("2016-11-08T22:41:15.912313785Z"),
"signature": null, // "signature": null,
'labels': [] // 'labels': []
} // }
]; // ];
const mockConfig: IServiceConfig = { const mockConfig: IServiceConfig = {
repositoryBaseEndpoint: "/api/repositories/testing" repositoryBaseEndpoint: "/api/repositories/testing"

View File

@ -1,167 +1,229 @@
import { Observable } from 'rxjs/Observable';
import { RequestQueryParams } from './RequestQueryParams';
import {Label, Tag} from './interface';
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of'; import { Http } from "@angular/http";
import { Http } from '@angular/http'; import { Observable } from "rxjs/Observable";
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import "rxjs/add/observable/of";
import { buildHttpRequestOptions, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from '../utils';
import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
import {
buildHttpRequestOptions,
HTTP_JSON_OPTIONS,
HTTP_GET_OPTIONS
} from "../utils";
import { RequestQueryParams } from "./RequestQueryParams";
import { Tag } from "./interface";
/** /**
* For getting tag signatures. * For getting tag signatures.
* This is temporary, will be removed in future. * This is temporary, will be removed in future.
* *
* @export * @export
* @class VerifiedSignature * @class VerifiedSignature
*/ */
export class VerifiedSignature { export class VerifiedSignature {
tag: string; tag: string;
hashes: { hashes: {
sha256: string; sha256: string;
} };
} }
/** /**
* Define the service methods to handle the repository tag related things. * Define the service methods to handle the repository tag related things.
* *
* @export * @export
* @abstract * @abstract
* @class TagService * @class TagService
*/ */
export abstract class TagService { export abstract class TagService {
/** /**
* Get all the tags under the specified repository. * Get all the tags under the specified repository.
* NOTES: If the Notary is enabled, the signatures should be included in the returned data. * NOTES: If the Notary is enabled, the signatures should be included in the returned data.
* *
* @abstract * @abstract
* @param {string} repositoryName * @param {string} repositoryName
* @param {RequestQueryParams} [queryParams] * @param {RequestQueryParams} [queryParams]
* @returns {(Observable<Tag[]> | Promise<Tag[]> | Tag[])} * @returns {(Observable<Tag[]> | Promise<Tag[]> | Tag[])}
* *
* @memberOf TagService * @memberOf TagService
*/ */
abstract getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable<Tag[]> | Promise<Tag[]> | Tag[]; abstract getTags(
repositoryName: string,
queryParams?: RequestQueryParams
): Observable<Tag[]> | Promise<Tag[]> | Tag[];
/** /**
* Delete the specified tag. * Delete the specified tag.
* *
* @abstract * @abstract
* @param {string} repositoryName * @param {string} repositoryName
* @param {string} tag * @param {string} tag
* @returns {(Observable<any> | any)} * @returns {(Observable<any> | any)}
* *
* @memberOf TagService * @memberOf TagService
*/ */
abstract deleteTag(repositoryName: string, tag: string): Observable<any> | Promise<any> | any; abstract deleteTag(
repositoryName: string,
tag: string
): Observable<any> | Promise<any> | any;
/** /**
* Get the specified tag. * Get the specified tag.
* *
* @abstract * @abstract
* @param {string} repositoryName * @param {string} repositoryName
* @param {string} tag * @param {string} tag
* @returns {(Observable<Tag> | Promise<Tag> | Tag)} * @returns {(Observable<Tag> | Promise<Tag> | Tag)}
* *
* @memberOf TagService * @memberOf TagService
*/ */
abstract getTag(repositoryName: string, tag: string, queryParams?: RequestQueryParams): Observable<Tag> | Promise<Tag> | Tag; abstract getTag(
repositoryName: string,
tag: string,
queryParams?: RequestQueryParams
): Observable<Tag> | Promise<Tag> | Tag;
abstract addLabelToImages(repoName: string, tagName: string, labelId: number): Observable<any> | Promise<any> | any; abstract addLabelToImages(
abstract deleteLabelToImages(repoName: string, tagName: string, labelId: number): Observable<any> | Promise<any> | any; repoName: string,
tagName: string,
labelId: number
): Observable<any> | Promise<any> | any;
abstract deleteLabelToImages(
repoName: string,
tagName: string,
labelId: number
): Observable<any> | Promise<any> | any;
} }
/** /**
* Implement default service for tag. * Implement default service for tag.
* *
* @export * @export
* @class TagDefaultService * @class TagDefaultService
* @extends {TagService} * @extends {TagService}
*/ */
@Injectable() @Injectable()
export class TagDefaultService extends TagService { export class TagDefaultService extends TagService {
_baseUrl: string; _baseUrl: string;
_labelUrl: string; _labelUrl: string;
constructor( constructor(
private http: Http, private http: Http,
@Inject(SERVICE_CONFIG) private config: IServiceConfig @Inject(SERVICE_CONFIG) private config: IServiceConfig
) { ) {
super(); super();
this._baseUrl = this.config.repositoryBaseEndpoint ? this.config.repositoryBaseEndpoint : '/api/repositories'; this._baseUrl = this.config.repositoryBaseEndpoint
this._labelUrl = this.config.labelEndpoint? this.config.labelEndpoint : '/api/labels'; ? this.config.repositoryBaseEndpoint
: "/api/repositories";
this._labelUrl = this.config.labelEndpoint
? this.config.labelEndpoint
: "/api/labels";
}
// Private methods
// These two methods are temporary, will be deleted in future after API refactored
_getTags(
repositoryName: string,
queryParams?: RequestQueryParams
): Promise<Tag[]> {
if (!queryParams) {
queryParams = new RequestQueryParams();
} }
//Private methods queryParams.set("detail", "1");
//These two methods are temporary, will be deleted in future after API refactored let url: string = `${this._baseUrl}/${repositoryName}/tags`;
_getTags(repositoryName: string, queryParams?: RequestQueryParams): Promise<Tag[]> {
if (!queryParams) {
queryParams = new RequestQueryParams();
}
queryParams.set('detail', '1'); return this.http
let url: string = `${this._baseUrl}/${repositoryName}/tags`; .get(url, buildHttpRequestOptions(queryParams))
.toPromise()
.then(response => response.json() as Tag[])
.catch(error => Promise.reject(error));
}
return this.http.get(url, buildHttpRequestOptions(queryParams)).toPromise() _getSignatures(repositoryName: string): Promise<VerifiedSignature[]> {
.then(response => response.json() as Tag[]) let url: string = `${this._baseUrl}/${repositoryName}/signatures`;
.catch(error => Promise.reject(error)); return this.http
.get(url, HTTP_GET_OPTIONS)
.toPromise()
.then(response => response.json() as VerifiedSignature[])
.catch(error => Promise.reject(error));
}
public getTags(
repositoryName: string,
queryParams?: RequestQueryParams
): Observable<Tag[]> | Promise<Tag[]> | Tag[] {
if (!repositoryName) {
return Promise.reject("Bad argument");
}
return this._getTags(repositoryName, queryParams);
}
public deleteTag(
repositoryName: string,
tag: string
): Observable<any> | Promise<Tag> | any {
if (!repositoryName || !tag) {
return Promise.reject("Bad argument");
} }
_getSignatures(repositoryName: string): Promise<VerifiedSignature[]> { let url: string = `${this._baseUrl}/${repositoryName}/tags/${tag}`;
let url: string = `${this._baseUrl}/${repositoryName}/signatures`; return this.http
return this.http.get(url, HTTP_GET_OPTIONS).toPromise() .delete(url, HTTP_JSON_OPTIONS)
.then(response => response.json() as VerifiedSignature[]) .toPromise()
.catch(error => Promise.reject(error)) .then(response => response)
.catch(error => Promise.reject(error));
}
public getTag(
repositoryName: string,
tag: string,
queryParams?: RequestQueryParams
): Observable<Tag> | Promise<Tag> | Tag {
if (!repositoryName || !tag) {
return Promise.reject("Bad argument");
} }
public getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable<Tag[]> | Promise<Tag[]> | Tag[] { let url: string = `${this._baseUrl}/${repositoryName}/tags/${tag}`;
if (!repositoryName) { return this.http
return Promise.reject("Bad argument"); .get(url, HTTP_GET_OPTIONS)
} .toPromise()
return this._getTags(repositoryName, queryParams); .then(response => response.json() as Tag)
.catch(error => Promise.reject(error));
}
public addLabelToImages(
repoName: string,
tagName: string,
labelId: number
): Observable<any> | Promise<any> | any {
if (!labelId || !tagName || !repoName) {
return Promise.reject("Invalid parameters.");
} }
public deleteTag(repositoryName: string, tag: string): Observable<any> | Promise<Tag> | any { let _addLabelToImageUrl = `${
if (!repositoryName || !tag) { this._baseUrl
return Promise.reject("Bad argument"); }/${repoName}/tags/${tagName}/labels`;
} return this.http
.post(_addLabelToImageUrl, { id: labelId }, HTTP_JSON_OPTIONS)
.toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
let url: string = `${this._baseUrl}/${repositoryName}/tags/${tag}`; public deleteLabelToImages(
return this.http.delete(url, HTTP_JSON_OPTIONS).toPromise() repoName: string,
.then(response => response) tagName: string,
.catch(error => Promise.reject(error)); labelId: number
): Observable<any> | Promise<any> | any {
if (!labelId || !tagName || !repoName) {
return Promise.reject("Invalid parameters.");
} }
public getTag(repositoryName: string, tag: string, queryParams?: RequestQueryParams): Observable<Tag> | Promise<Tag> | Tag { let _addLabelToImageUrl = `${
if (!repositoryName || !tag) { this._baseUrl
return Promise.reject("Bad argument"); }/${repoName}/tags/${tagName}/labels/${labelId}`;
} return this.http
.delete(_addLabelToImageUrl)
let url: string = `${this._baseUrl}/${repositoryName}/tags/${tag}`; .toPromise()
return this.http.get(url, HTTP_GET_OPTIONS).toPromise() .then(response => response.status)
.then(response => response.json() as Tag) .catch(error => Promise.reject(error));
.catch(error => Promise.reject(error)); }
} }
public addLabelToImages(repoName: string, tagName: string, labelId: number): Observable<any> | Promise<any> | any {
if (!labelId || !tagName || !repoName) {
return Promise.reject('Invalid parameters.');
}
let _addLabelToImageUrl = `${this._baseUrl}/${repoName}/tags/${tagName}/labels`;
return this.http.post(_addLabelToImageUrl, {id: labelId}, HTTP_JSON_OPTIONS).toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
public deleteLabelToImages(repoName: string, tagName: string, labelId: number): Observable<any> | Promise<any> | any {
if (!labelId || !tagName || !repoName) {
return Promise.reject('Invalid parameters.');
}
let _addLabelToImageUrl = `${this._baseUrl}/${repoName}/tags/${tagName}/labels/${labelId}`;
return this.http.delete(_addLabelToImageUrl).toPromise()
.then(response => response.status)
.catch(error => Promise.reject(error));
}
}

View File

@ -29,15 +29,15 @@ export const httpStatusCode = {
"Forbidden": 403 "Forbidden": 403
}; };
export const enum ConfirmationTargets { export const enum ConfirmationTargets {
EMPTY, EMPTY,
PROJECT, PROJECT,
PROJECT_MEMBER, PROJECT_MEMBER,
USER, USER,
POLICY, POLICY,
TOGGLE_CONFIRM, TOGGLE_CONFIRM,
TARGET, TARGET,
REPOSITORY, REPOSITORY,
TAG, TAG,
CONFIG, CONFIG,
CONFIG_ROUTE, CONFIG_ROUTE,
CONFIG_TAB CONFIG_TAB
@ -70,12 +70,20 @@ export const enum ConfirmationButtons {
}; };
export const LabelColor = [ export const LabelColor = [
{'color': '#000000', 'textColor' : 'white'}, {'color': '#61717D', 'textColor': 'white'}, {'color': '#737373', 'textColor' : 'white'}, {'color': '#80746D', 'textColor': 'white'}, { 'color': '#000000', 'textColor': 'white' }, { 'color': '#61717D', 'textColor': 'white' },
{'color': '#FFFFFF', 'textColor' : 'black'}, {'color': '#A9B6BE', 'textColor': 'black'}, {'color': '#DDDDDD', 'textColor' : 'black'}, {'color': '#BBB3A9', 'textColor': 'black'}, { 'color': '#737373', 'textColor': 'white' }, { 'color': '#80746D', 'textColor': 'white' },
{'color': '#0065AB', 'textColor' : 'white'}, {'color': '#343DAC', 'textColor': 'white'}, {'color': '#781DA0', 'textColor' : 'white'}, {'color': '#9B0D54', 'textColor': 'white'}, { 'color': '#FFFFFF', 'textColor': 'black' }, { 'color': '#A9B6BE', 'textColor': 'black' },
{'color': '#0095D3', 'textColor' : 'black'}, {'color': '#9DA3DB', 'textColor': 'black'}, {'color': '#BE90D6', 'textColor' : 'black'}, {'color': '#F1428A', 'textColor': 'black'}, { 'color': '#DDDDDD', 'textColor': 'black' }, { 'color': '#BBB3A9', 'textColor': 'black' },
{'color': '#1D5100', 'textColor' : 'white'}, {'color': '#006668', 'textColor': 'white'}, {'color': '#006690', 'textColor' : 'white'}, {'color': '#004A70', 'textColor': 'white'}, { 'color': '#0065AB', 'textColor': 'white' }, { 'color': '#343DAC', 'textColor': 'white' },
{'color': '#48960C', 'textColor' : 'black'}, {'color': '#00AB9A', 'textColor': 'black'}, {'color': '#00B7D6', 'textColor' : 'black'}, {'color': '#0081A7', 'textColor': 'black'}, { 'color': '#781DA0', 'textColor': 'white' }, { 'color': '#9B0D54', 'textColor': 'white' },
{'color': '#C92100', 'textColor' : 'white'}, {'color': '#CD3517', 'textColor': 'white'}, {'color': '#C25400', 'textColor' : 'white'}, {'color': '#D28F00', 'textColor': 'white'}, { 'color': '#0095D3', 'textColor': 'black' }, { 'color': '#9DA3DB', 'textColor': 'black' },
{'color': '#F52F52', 'textColor' : 'black'}, {'color': '#FF5501', 'textColor': 'black'}, {'color': '#F57600', 'textColor' : 'black'}, {'color': '#FFDC0B', 'textColor': 'black'}, { 'color': '#BE90D6', 'textColor': 'black' }, { 'color': '#F1428A', 'textColor': 'black' },
{ 'color': '#1D5100', 'textColor': 'white' }, { 'color': '#006668', 'textColor': 'white' },
{ 'color': '#006690', 'textColor': 'white' }, { 'color': '#004A70', 'textColor': 'white' },
{ 'color': '#48960C', 'textColor': 'black' }, { 'color': '#00AB9A', 'textColor': 'black' },
{ 'color': '#00B7D6', 'textColor': 'black' }, { 'color': '#0081A7', 'textColor': 'black' },
{ 'color': '#C92100', 'textColor': 'white' }, { 'color': '#CD3517', 'textColor': 'white' },
{ 'color': '#C25400', 'textColor': 'white' }, { 'color': '#D28F00', 'textColor': 'white' },
{ 'color': '#F52F52', 'textColor': 'black' }, { 'color': '#FF5501', 'textColor': 'black' },
{ 'color': '#F57600', 'textColor': 'black' }, { 'color': '#FFDC0B', 'textColor': 'black' },
]; ];

View File

@ -11,8 +11,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { NgForm } from '@angular/forms';
import { httpStatusCode, AlertType } from './shared.const';
/** /**
* To handle the error message body * To handle the error message body
* *
@ -46,7 +44,7 @@ export const errorHandler = function (error: any): string {
return "UNKNOWN_ERROR"; return "UNKNOWN_ERROR";
} }
} }
} };
export class CancelablePromise<T> { export class CancelablePromise<T> {
@ -70,4 +68,4 @@ export class CancelablePromise<T> {
cancel() { cancel() {
this.isCanceled = true; this.isCanceled = true;
} }
} }

View File

@ -8,4 +8,4 @@ export * from './tag-detail.component';
export const TAG_DIRECTIVES: Type<any>[] = [ export const TAG_DIRECTIVES: Type<any>[] = [
TagComponent, TagComponent,
TagDetailComponent TagDetailComponent
]; ];

View File

@ -148,4 +148,4 @@ describe('TagDetailComponent (inline template)', () => {
}); });
})); }));
}); });

View File

@ -70,7 +70,7 @@ export class TagDetailComponent implements OnInit {
}); });
} }
}) })
.catch(error => this.errorHandler.error(error)) .catch(error => this.errorHandler.error(error));
} }
} }

View File

@ -17,7 +17,7 @@
<div id="filterArea"> <div id="filterArea">
<div class='filterLabelPiece' *ngIf="!withAdmiral" [hidden]="!openLabelFilterPiece" [style.left.px]='filterLabelPieceWidth' ><hbr-label-piece [hidden]='!filterOneLabel' [label]="filterOneLabel" [labelWidth]="130"></hbr-label-piece></div> <div class='filterLabelPiece' *ngIf="!withAdmiral" [hidden]="!openLabelFilterPiece" [style.left.px]='filterLabelPieceWidth' ><hbr-label-piece [hidden]='!filterOneLabel' [label]="filterOneLabel" [labelWidth]="130"></hbr-label-piece></div>
<div class="flex-xs-middle"> <div class="flex-xs-middle">
<hbr-filter [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filter)="doSearchTagNames($event)" (openFlag)="openFlagEvent($event)" [currentValue]="lastFilteredTagName"></hbr-filter> <hbr-filter [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filterEvt)="doSearchTagNames($event)" (openFlag)="openFlagEvent($event)" [currentValue]="lastFilteredTagName"></hbr-filter>
<div class="labelFilterPanel" *ngIf="!withAdmiral" [hidden]="!openLabelFilterPanel"> <div class="labelFilterPanel" *ngIf="!withAdmiral" [hidden]="!openLabelFilterPanel">
<a class="filterClose" (click)="closeFilter()">&times;</a> <a class="filterClose" (click)="closeFilter()">&times;</a>
<label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label> <label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label>

View File

@ -1,7 +1,5 @@
import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing'; import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { Router } from '@angular/router';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
@ -12,9 +10,8 @@ import {Label, Tag} from '../service/interface';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { TagService, TagDefaultService, ScanningResultService, ScanningResultDefaultService } from '../service/index'; import { TagService, TagDefaultService, ScanningResultService, ScanningResultDefaultService } from '../service/index';
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index'; import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
import { FILTER_DIRECTIVES } from '../filter/index' import { FILTER_DIRECTIVES } from '../filter/index';
import { Observable, Subscription } from 'rxjs/Rx';
import { ChannelService } from '../channel/index'; import { ChannelService } from '../channel/index';
import { JobLogViewerComponent } from '../job-log-viewer/index'; import { JobLogViewerComponent } from '../job-log-viewer/index';
@ -163,4 +160,4 @@ describe('TagComponent (inline template)', () => {
}); });
})); }));
}); });

View File

@ -18,9 +18,8 @@ import {
Input, Input,
Output, Output,
EventEmitter, EventEmitter,
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
ElementRef, AfterContentInit, AfterViewInit ElementRef, AfterViewInit
} from "@angular/core"; } from "@angular/core";
import { TagService, VulnerabilitySeverity, RequestQueryParams } from "../service/index"; import { TagService, VulnerabilitySeverity, RequestQueryParams } from "../service/index";
@ -193,7 +192,7 @@ export class TagComponent implements OnInit, AfterViewInit {
}else { }else {
data.show = false; data.show = false;
} }
}) });
setTimeout(() => { setTimeout(() => {
setInterval(() => this.ref.markForCheck(), 200); setInterval(() => this.ref.markForCheck(), 200);
}, 1000); }, 1000);
@ -303,7 +302,7 @@ export class TagComponent implements OnInit, AfterViewInit {
this.imageStickLabels.forEach(data => { this.imageStickLabels.forEach(data => {
data.iconsShow = false; data.iconsShow = false;
data.show = true; data.show = true;
}) });
if (tag[0].labels.length) { if (tag[0].labels.length) {
tag[0].labels.forEach((labelInfo: Label) => { tag[0].labels.forEach((labelInfo: Label) => {
let findedLabel = this.imageStickLabels.find(data => labelInfo.id === data['label'].id); let findedLabel = this.imageStickLabels.find(data => labelInfo.id === data['label'].id);
@ -388,7 +387,6 @@ export class TagComponent implements OnInit, AfterViewInit {
} }
filterLabel(labelInfo: LabelState): void { filterLabel(labelInfo: LabelState): void {
let labelName = labelInfo.label.name;
let labelId = labelInfo.label.id; let labelId = labelInfo.label.id;
// insert the unselected label to groups with the same icons // insert the unselected label to groups with the same icons
let preLabelInfo = this.imageFilterLabels.find(data => data.label.id === this.filterOneLabel.id); let preLabelInfo = this.imageFilterLabels.find(data => data.label.id === this.filterOneLabel.id);
@ -613,7 +611,8 @@ export class TagComponent implements OnInit, AfterViewInit {
if (signature) { if (signature) {
Observable.forkJoin(this.translateService.get("BATCH.DELETED_FAILURE"), Observable.forkJoin(this.translateService.get("BATCH.DELETED_FAILURE"),
this.translateService.get("REPOSITORY.DELETION_SUMMARY_TAG_DENIED")).subscribe(res => { this.translateService.get("REPOSITORY.DELETION_SUMMARY_TAG_DENIED")).subscribe(res => {
let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl + ":4443 -d ~/.docker/trust remove -p " + this.registryUrl + "/" + this.repoName + " " + name; let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl + ":4443 -d ~/.docker/trust remove -p " +
this.registryUrl + "/" + this.repoName + " " + name;
findedList = BathInfoChanges(findedList, res[0], false, true, wrongInfo); findedList = BathInfoChanges(findedList, res[0], false, true, wrongInfo);
}); });
} else { } else {

View File

@ -1,2 +1,2 @@
export * from './ngx-window-token/index'; export * from './ngx-window-token/index';
export * from './ngx-clipboard/index'; export * from './ngx-clipboard/index';

View File

@ -5,6 +5,7 @@ import { Directive, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output
selector: '[ngxClipboard]' selector: '[ngxClipboard]'
}) })
export class ClipboardDirective implements OnInit, OnDestroy { export class ClipboardDirective implements OnInit, OnDestroy {
// tslint:disable-next-line:no-input-rename
@Input('ngxClipboard') public targetElm: HTMLInputElement; @Input('ngxClipboard') public targetElm: HTMLInputElement;
@Input() public cbContent: string; @Input() public cbContent: string;
@ -24,7 +25,9 @@ export class ClipboardDirective implements OnInit, OnDestroy {
this.clipboardSrv.destroy(); this.clipboardSrv.destroy();
} }
@HostListener('click', ['$event.target']) private onClick(button: ElementRef) { @HostListener('click', ['$event.target'])
// tslint:disable-next-line:no-unused-variable
private onClick(button: ElementRef) {
if (!this.clipboardSrv.isSupported) { if (!this.clipboardSrv.isSupported) {
this.handleResult(false, undefined); this.handleResult(false, undefined);
} else if (this.targetElm && this.clipboardSrv.isTargetValid(this.targetElm)) { } else if (this.targetElm && this.clipboardSrv.isTargetValid(this.targetElm)) {
@ -46,4 +49,4 @@ export class ClipboardDirective implements OnInit, OnDestroy {
this.cbOnError.emit({ isSuccess: false }); this.cbOnError.emit({ isSuccess: false });
} }
} }
} }

View File

@ -1,110 +1,144 @@
import { Inject, InjectionToken, Injectable, Optional, Renderer, SkipSelf } from '@angular/core'; import {
import { DOCUMENT } from '@angular/platform-browser'; Inject,
// tslint:disable-next-line:no-unused-variable
InjectionToken,
Injectable,
Optional,
Renderer,
SkipSelf
} from "@angular/core";
import { DOCUMENT } from "@angular/platform-browser";
import { WINDOW } from "../ngx-window-token/index"; import { WINDOW } from "../ngx-window-token/index";
@Injectable() @Injectable()
export class ClipboardService { export class ClipboardService {
private tempTextArea: HTMLTextAreaElement; private tempTextArea: HTMLTextAreaElement;
constructor( constructor(
@Inject(DOCUMENT) private document: any, @Inject(DOCUMENT) private document: any,
@Inject(WINDOW) private window: any, @Inject(WINDOW) private window: any
) { } ) {}
public get isSupported(): boolean { public get isSupported(): boolean {
return !!this.document.queryCommandSupported && !!this.document.queryCommandSupported('copy'); return (
} !!this.document.queryCommandSupported &&
!!this.document.queryCommandSupported("copy")
);
}
public isTargetValid(element: HTMLInputElement | HTMLTextAreaElement): boolean { public isTargetValid(
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) { element: HTMLInputElement | HTMLTextAreaElement
if (element.hasAttribute('disabled')) { ): boolean {
// tslint:disable-next-line:max-line-length if (
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); element instanceof HTMLInputElement ||
} element instanceof HTMLTextAreaElement
return true; ) {
} if (element.hasAttribute("disabled")) {
throw new Error('Target should be input or textarea'); throw new Error(
'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'
);
}
return true;
} }
throw new Error("Target should be input or textarea");
}
/** /**
* copyFromInputElement * copyFromInputElement
*/ */
public copyFromInputElement(targetElm: HTMLInputElement | HTMLTextAreaElement, renderer: Renderer): boolean { public copyFromInputElement(
try { targetElm: HTMLInputElement | HTMLTextAreaElement,
this.selectTarget(targetElm, renderer); renderer: Renderer
const re = this.copyText(); ): boolean {
this.clearSelection(targetElm, this.window); try {
return re; this.selectTarget(targetElm, renderer);
} catch (error) { const re = this.copyText();
return false; this.clearSelection(targetElm, this.window);
} return re;
} catch (error) {
return false;
} }
}
/** /**
* Creates a fake textarea element, sets its value from `text` property, * Creates a fake textarea element, sets its value from `text` property,
* and makes a selection on it. * and makes a selection on it.
*/ */
public copyFromContent(content: string, renderer: Renderer) { public copyFromContent(content: string, renderer: Renderer) {
if (!this.tempTextArea) { if (!this.tempTextArea) {
this.tempTextArea = this.createTempTextArea(this.document, this.window); this.tempTextArea = this.createTempTextArea(this.document, this.window);
this.document.body.appendChild(this.tempTextArea); this.document.body.appendChild(this.tempTextArea);
}
this.tempTextArea.value = content;
return this.copyFromInputElement(this.tempTextArea, renderer);
} }
this.tempTextArea.value = content;
return this.copyFromInputElement(this.tempTextArea, renderer);
}
// remove temporary textarea if any // remove temporary textarea if any
public destroy() { public destroy() {
if (this.tempTextArea) { if (this.tempTextArea) {
this.document.body.removeChild(this.tempTextArea); this.document.body.removeChild(this.tempTextArea);
this.tempTextArea = undefined; this.tempTextArea = undefined;
}
} }
}
// select the target html input element // select the target html input element
private selectTarget(inputElement: HTMLInputElement | HTMLTextAreaElement, renderer: Renderer): number | undefined { private selectTarget(
renderer.invokeElementMethod(inputElement, 'select'); inputElement: HTMLInputElement | HTMLTextAreaElement,
renderer.invokeElementMethod(inputElement, 'setSelectionRange', [0, inputElement.value.length]); renderer: Renderer
return inputElement.value.length; ): number | undefined {
} renderer.invokeElementMethod(inputElement, "select");
renderer.invokeElementMethod(inputElement, "setSelectionRange", [
0,
inputElement.value.length
]);
return inputElement.value.length;
}
private copyText(): boolean { private copyText(): boolean {
return this.document.execCommand('copy'); return this.document.execCommand("copy");
} }
// Removes current selection and focus from `target` element. // Removes current selection and focus from `target` element.
private clearSelection(inputElement: HTMLInputElement | HTMLTextAreaElement, window: Window) { private clearSelection(
// tslint:disable-next-line:no-unused-expression inputElement: HTMLInputElement | HTMLTextAreaElement,
inputElement && inputElement.blur(); window: Window
window.getSelection().removeAllRanges(); ) {
} if (inputElement) { inputElement.blur(); }
window.getSelection().removeAllRanges();
}
// create a fake textarea for copy command // create a fake textarea for copy command
private createTempTextArea(doc: Document, window: Window): HTMLTextAreaElement { private createTempTextArea(
const isRTL = doc.documentElement.getAttribute('dir') === 'rtl'; doc: Document,
let ta: HTMLTextAreaElement; window: Window
ta = doc.createElement('textarea'); ): HTMLTextAreaElement {
// Prevent zooming on iOS const isRTL = doc.documentElement.getAttribute("dir") === "rtl";
ta.style.fontSize = '12pt'; let ta: HTMLTextAreaElement;
// Reset box model ta = doc.createElement("textarea");
ta.style.border = '0'; // Prevent zooming on iOS
ta.style.padding = '0'; ta.style.fontSize = "12pt";
ta.style.margin = '0'; // Reset box model
// Move element out of screen horizontally ta.style.border = "0";
ta.style.position = 'absolute'; ta.style.padding = "0";
ta.style[isRTL ? 'right' : 'left'] = '-9999px'; ta.style.margin = "0";
// Move element to the same position vertically // Move element out of screen horizontally
let yPosition = window.pageYOffset || doc.documentElement.scrollTop; ta.style.position = "absolute";
ta.style.top = yPosition + 'px'; ta.style[isRTL ? "right" : "left"] = "-9999px";
ta.setAttribute('readonly', ''); // Move element to the same position vertically
return ta; let yPosition = window.pageYOffset || doc.documentElement.scrollTop;
} ta.style.top = yPosition + "px";
ta.setAttribute("readonly", "");
return ta;
}
} }
// this pattern is mentioned in https://github.com/angular/angular/issues/13854 in #43 // this pattern is mentioned in https://github.com/angular/angular/issues/13854 in #43
export function CLIPBOARD_SERVICE_PROVIDER_FACTORY(doc: Document, win: Window, parentDispatcher: ClipboardService) { export function CLIPBOARD_SERVICE_PROVIDER_FACTORY(
return parentDispatcher || new ClipboardService(doc, win); doc: Document,
}; win: Window,
parentDispatcher: ClipboardService
) {
return parentDispatcher || new ClipboardService(doc, win);
}
export const CLIPBOARD_SERVICE_PROVIDER = { export const CLIPBOARD_SERVICE_PROVIDER = {
provide: ClipboardService, provide: ClipboardService,
deps: [DOCUMENT, WINDOW, [new Optional(), new SkipSelf(), ClipboardService]], deps: [DOCUMENT, WINDOW, [new Optional(), new SkipSelf(), ClipboardService]],
useFactory: CLIPBOARD_SERVICE_PROVIDER_FACTORY useFactory: CLIPBOARD_SERVICE_PROVIDER_FACTORY
}; };

Some files were not shown because too many files have changed in this diff Show More