mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 10:45:45 +01:00
Add shareable endpoint components.
This commit is contained in:
parent
c3c7b540f1
commit
5071dcf304
@ -0,0 +1,21 @@
|
||||
export const CONFIRMATION_DIALOG_STYLE: string = `
|
||||
.confirmation-icon-inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.confirmation-title {
|
||||
line-height: 24px;
|
||||
color: #000000;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.confirmation-content {
|
||||
font-size: 14px;
|
||||
color: #565656;
|
||||
line-height: 24px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 80%;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
`;
|
@ -0,0 +1,28 @@
|
||||
export const CONFIRMATION_DIALOG_TEMPLATE: string = `
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title" class="confirmation-title" style="margin-top: 0px;">{{dialogTitle}}</h3>
|
||||
<div class="modal-body">
|
||||
<div class="confirmation-icon-inline">
|
||||
<clr-icon shape="warning" class="is-warning" size="64"></clr-icon>
|
||||
</div>
|
||||
<div class="confirmation-content">{{dialogContent}}</div>
|
||||
</div>
|
||||
<div class="modal-footer" [ngSwitch]="buttons">
|
||||
<ng-template [ngSwitchCase]="0">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirm()">{{ 'BUTTON.CONFIRM' | translate}}</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="1">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.NO' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirm()">{{ 'BUTTON.YES' | translate}}</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="2">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-danger" (click)="confirm()">{{ 'BUTTON.DELETE' | translate}}</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="3">
|
||||
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</clr-modal>
|
||||
`;
|
@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ConfirmationMessage } from './confirmation-message';
|
||||
import { ConfirmationAcknowledgement } from './confirmation-state-message';
|
||||
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
|
||||
|
||||
import { CONFIRMATION_DIALOG_TEMPLATE } from './confirmation-dialog.component.html';
|
||||
import { CONFIRMATION_DIALOG_STYLE } from './confirmation-dialog.component.css';
|
||||
|
||||
@Component({
|
||||
selector: 'confirmation-dialog',
|
||||
template: CONFIRMATION_DIALOG_TEMPLATE,
|
||||
styles: [ CONFIRMATION_DIALOG_STYLE ]
|
||||
})
|
||||
|
||||
export class ConfirmationDialogComponent {
|
||||
opened: boolean = false;
|
||||
dialogTitle: string = "";
|
||||
dialogContent: string = "";
|
||||
message: ConfirmationMessage;
|
||||
buttons: ConfirmationButtons;
|
||||
|
||||
@Output() confirmAction = new EventEmitter<ConfirmationAcknowledgement>();
|
||||
@Output() cancelAction = new EventEmitter<ConfirmationAcknowledgement>();
|
||||
|
||||
constructor(
|
||||
private translate: TranslateService) {}
|
||||
|
||||
open(msg: ConfirmationMessage): void {
|
||||
this.dialogTitle = msg.title;
|
||||
this.dialogContent = msg.message;
|
||||
this.message = msg;
|
||||
this.translate.get(this.dialogTitle).subscribe((res: string) => this.dialogTitle = res);
|
||||
this.translate.get(this.dialogContent, { 'param': msg.param }).subscribe((res: string) => this.dialogContent = res);
|
||||
//Open dialog
|
||||
this.buttons = msg.buttons;
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if(!this.message){//Inproper condition
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let data: any = this.message.data ? this.message.data : {};
|
||||
let target = this.message.targetId ? this.message.targetId : ConfirmationTargets.EMPTY;
|
||||
this.cancelAction.emit(new ConfirmationAcknowledgement(
|
||||
ConfirmationState.CANCEL,
|
||||
data,
|
||||
target
|
||||
));
|
||||
this.close();
|
||||
}
|
||||
|
||||
confirm(): void {
|
||||
if(!this.message){//Inproper condition
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let data: any = this.message.data ? this.message.data : {};
|
||||
let target = this.message.targetId ? this.message.targetId : ConfirmationTargets.EMPTY;
|
||||
let message = new ConfirmationAcknowledgement(
|
||||
ConfirmationState.CONFIRMED,
|
||||
data,
|
||||
target
|
||||
);
|
||||
this.confirmAction.emit(message);
|
||||
this.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
|
||||
|
||||
export class ConfirmationMessage {
|
||||
public constructor(title: string, message: string, param: string, data: any, targetId: ConfirmationTargets, buttons?: ConfirmationButtons) {
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
this.targetId = targetId;
|
||||
this.param = param;
|
||||
this.buttons = buttons ? buttons : ConfirmationButtons.CONFIRM_CANCEL;
|
||||
}
|
||||
title: string;
|
||||
message: string;
|
||||
data: any = {};//default is empty
|
||||
targetId: ConfirmationTargets = ConfirmationTargets.EMPTY;
|
||||
param: string;
|
||||
buttons: ConfirmationButtons;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { ConfirmationState, ConfirmationTargets } from '../shared/shared.const';
|
||||
|
||||
export class ConfirmationAcknowledgement {
|
||||
constructor(state: ConfirmationState, data: any, source: ConfirmationTargets) {
|
||||
this.state = state;
|
||||
this.data = data;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
state: ConfirmationState = ConfirmationState.NA;
|
||||
data: any = {};
|
||||
source: ConfirmationTargets = ConfirmationTargets.EMPTY;
|
||||
}
|
7
src/ui_ng/lib/src/confirmation-dialog/index.ts
Normal file
7
src/ui_ng/lib/src/confirmation-dialog/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
import { ConfirmationDialogComponent } from './confirmation-dialog.component';
|
||||
|
||||
export const CONFIRMATION_DIALOG_DIRECTIVES: Type<any>[] = [
|
||||
ConfirmationDialogComponent
|
||||
];
|
@ -0,0 +1,6 @@
|
||||
export const CREATE_EDIT_ENDPOINT_STYLE: string = `
|
||||
.form-group-label-override {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
`;
|
@ -0,0 +1,53 @@
|
||||
export const CREATE_EDIT_ENDPOINT_TEMPLATE: string = `<clr-modal [(clrModalOpen)]="createEditDestinationOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||
<h3 class="modal-title">{{modalTitle}}</h3>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning" *ngIf="!editable">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">
|
||||
{{'DESTINATION.CANNOT_EDIT' | translate}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<form #targetForm="ngForm">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="destination_name" class="col-md-4 form-group-label-override">{{ 'DESTINATION.NAME' | translate }}<span style="color: red">*</span></label>
|
||||
<label class="col-md-8" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="targetName.errors && (targetName.dirty || targetName.touched)" [class.valid]="targetName.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
|
||||
<input type="text" id="destination_name" [disabled]="testOngoing" [readonly]="!editable" [(ngModel)]="target.name" name="targetName" size="20" #targetName="ngModel" required (keyup)="changedTargetName($event)">
|
||||
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
|
||||
{{ 'DESTINATION.NAME_IS_REQUIRED' | translate }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_url" class="col-md-4 form-group-label-override">{{ 'DESTINATION.URL' | translate }}<span style="color: red">*</span></label>
|
||||
<label class="col-md-8" for="destination_url" aria-haspopup="true" role="tooltip" [class.invalid]="targetEndpoint.errors && (targetEndpoint.dirty || targetEndpoint.touched)" [class.valid]="targetEndpoint.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
|
||||
<input type="text" id="destination_url" [disabled]="testOngoing" [readonly]="!editable" [(ngModel)]="target.endpoint" size="20" name="endpointUrl" #targetEndpoint="ngModel" required (keyup)="clearPassword($event)">
|
||||
<span class="tooltip-content" *ngIf="targetEndpoint.errors && targetEndpoint.errors.required && (targetEndpoint.dirty || targetEndpoint.touched)">
|
||||
{{ 'DESTINATION.URL_IS_REQUIRED' | translate }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_username" class="col-md-4 form-group-label-override">{{ 'DESTINATION.USERNAME' | translate }}</label>
|
||||
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing" [readonly]="!editable" [(ngModel)]="target.username" size="20" name="username" #username="ngModel" (keyup)="clearPassword($event)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_password" class="col-md-4 form-group-label-override">{{ 'DESTINATION.PASSWORD' | translate }}</label>
|
||||
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [readonly]="!editable" [(ngModel)]="target.password" size="20" name="password" #password="ngModel" (focus)="clearPassword($event)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spin" class="col-md-4"></label>
|
||||
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
||||
<span [style.color]="!pingStatus ? 'red': ''" class="form-group-label-override">{{ pingTestMessage }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="testConnection()" [disabled]="testOngoing || targetEndpoint.errors">{{ 'DESTINATION.TEST_CONNECTION' | translate }}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="onCancel()" [disabled]="testOngoing">{{ 'BUTTON.CANCEL' | translate }}</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="onSubmit()" [disabled]="testOngoing || targetForm.form.invalid || !editable">{{ 'BUTTON.OK' | translate }}</button>
|
||||
</div>
|
||||
</clr-modal>`;
|
@ -0,0 +1,96 @@
|
||||
import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
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)', () => {
|
||||
|
||||
let mockData: Endpoint = {
|
||||
"id": 1,
|
||||
"endpoint": "https://10.117.4.151",
|
||||
"name": "target_01",
|
||||
"username": "admin",
|
||||
"password": "",
|
||||
"type": 0
|
||||
};
|
||||
|
||||
let comp: CreateEditEndpointComponent;
|
||||
let fixture: ComponentFixture<CreateEditEndpointComponent>;
|
||||
let de: DebugElement;
|
||||
let el: HTMLElement;
|
||||
|
||||
let config: IServiceConfig = {
|
||||
systemInfoEndpoint: '/api/endpoints/testing'
|
||||
};
|
||||
|
||||
let endpointService: EndpointService;
|
||||
|
||||
let spy: jasmine.Spy;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ SharedModule ],
|
||||
declarations: [
|
||||
FilterComponent,
|
||||
CreateEditEndpointComponent,
|
||||
InlineAlertComponent ],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService },
|
||||
{ provide: TranslateService, useClass: TranslateService}
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(()=>{
|
||||
fixture = TestBed.createComponent(CreateEditEndpointComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
endpointService = fixture.debugElement.injector.get(EndpointService);
|
||||
spy = spyOn(endpointService, 'getEndpoint').and.returnValue(Promise.resolve(mockData));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
fixture.detectChanges();
|
||||
expect(comp).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get endpoint be called', async(()=>{
|
||||
fixture.detectChanges();
|
||||
comp.openCreateEditTarget(true, 1);
|
||||
comp.createEditDestinationOpened = false;
|
||||
fixture.whenStable().then(()=>{
|
||||
fixture.detectChanges();
|
||||
expect(spy.calls.any()).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should get endpoint to open modal', async(()=>{
|
||||
fixture.detectChanges();
|
||||
comp.openCreateEditTarget(true, 1);
|
||||
comp.createEditDestinationOpened = false;
|
||||
fixture.whenStable().then(()=>{
|
||||
fixture.detectChanges();
|
||||
expect(comp.target.name).toEqual('target_01');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should endpoint be initialized', () => {
|
||||
fixture.detectChanges();
|
||||
expect(config.systemInfoEndpoint).toEqual('/api/endpoints/testing');
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,301 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, Output, EventEmitter, ViewChild, AfterViewChecked } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { EndpointService } from '../service/endpoint.service';
|
||||
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 { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { CREATE_EDIT_ENDPOINT_STYLE } from './create-edit-endpoint.component.css';
|
||||
import { CREATE_EDIT_ENDPOINT_TEMPLATE } from './create-edit-endpoint.component.html';
|
||||
|
||||
|
||||
import { toPromise } from '../utils';
|
||||
|
||||
const FAKE_PASSWORD = 'rjGcfuRu';
|
||||
|
||||
@Component({
|
||||
selector: 'create-edit-endpoint',
|
||||
template: CREATE_EDIT_ENDPOINT_TEMPLATE,
|
||||
styles: [ CREATE_EDIT_ENDPOINT_STYLE ]
|
||||
})
|
||||
export class CreateEditEndpointComponent implements AfterViewChecked {
|
||||
|
||||
modalTitle: string;
|
||||
createEditDestinationOpened: boolean;
|
||||
|
||||
editable: boolean;
|
||||
|
||||
testOngoing: boolean;
|
||||
pingTestMessage: string;
|
||||
pingStatus: boolean;
|
||||
|
||||
actionType: ActionType;
|
||||
|
||||
target: Endpoint = Object.assign({}, this.initEndpoint);
|
||||
initVal: Endpoint = Object.assign({}, this.initEndpoint);
|
||||
|
||||
targetForm: NgForm;
|
||||
|
||||
staticBackdrop: boolean = true;
|
||||
closable: boolean = false;
|
||||
|
||||
@ViewChild('targetForm')
|
||||
currentForm: NgForm;
|
||||
|
||||
hasChanged: boolean;
|
||||
|
||||
endpointHasChanged: boolean;
|
||||
targetNameHasChanged: boolean;
|
||||
|
||||
@ViewChild(InlineAlertComponent)
|
||||
inlineAlert: InlineAlertComponent;
|
||||
|
||||
@Output() reload = new EventEmitter<boolean>();
|
||||
|
||||
|
||||
get initEndpoint(): Endpoint {
|
||||
return {
|
||||
endpoint: "",
|
||||
name: "",
|
||||
username: "",
|
||||
password: "",
|
||||
type: 0
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private endpointService: EndpointService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private translateService: TranslateService) {}
|
||||
|
||||
openCreateEditTarget(editable: boolean, targetId?: number) {
|
||||
|
||||
this.target = Object.assign({}, this.initEndpoint);
|
||||
this.editable = editable;
|
||||
this.createEditDestinationOpened = true;
|
||||
this.hasChanged = false;
|
||||
this.endpointHasChanged = false;
|
||||
this.targetNameHasChanged = false;
|
||||
|
||||
this.pingTestMessage = '';
|
||||
this.pingStatus = true;
|
||||
this.testOngoing = false;
|
||||
|
||||
if(targetId) {
|
||||
this.actionType = ActionType.EDIT;
|
||||
this.translateService.get('DESTINATION.TITLE_EDIT').subscribe(res=>this.modalTitle=res);
|
||||
toPromise<Endpoint>(this.endpointService
|
||||
.getEndpoint(targetId))
|
||||
.then(
|
||||
target=>{
|
||||
this.target = target;
|
||||
this.initVal.name = this.target.name;
|
||||
this.initVal.endpoint = this.target.endpoint;
|
||||
this.initVal.username = this.target.username;
|
||||
this.initVal.password = FAKE_PASSWORD;
|
||||
this.target.password = this.initVal.password;
|
||||
|
||||
})
|
||||
.catch(error=>this.errorHandler.error(error));
|
||||
} else {
|
||||
this.actionType = ActionType.ADD_NEW;
|
||||
this.translateService.get('DESTINATION.TITLE_ADD').subscribe(res=>this.modalTitle=res);
|
||||
}
|
||||
}
|
||||
|
||||
testConnection() {
|
||||
this.translateService.get('DESTINATION.TESTING_CONNECTION').subscribe(res=>this.pingTestMessage=res);
|
||||
this.pingStatus = true;
|
||||
this.testOngoing = !this.testOngoing;
|
||||
|
||||
let payload: Endpoint = Object.assign({}, this.initEndpoint);;
|
||||
if(this.endpointHasChanged) {
|
||||
payload.endpoint = this.target.endpoint;
|
||||
payload.username = this.target.username;
|
||||
payload.password = this.target.password;
|
||||
} else {
|
||||
payload.id = this.target.id;
|
||||
}
|
||||
|
||||
toPromise<Endpoint>(this.endpointService
|
||||
.pingEndpoint(payload))
|
||||
.then(
|
||||
response=>{
|
||||
this.pingStatus = true;
|
||||
this.translateService.get('DESTINATION.TEST_CONNECTION_SUCCESS').subscribe(res=>this.pingTestMessage=res);
|
||||
this.testOngoing = !this.testOngoing;
|
||||
}).catch(
|
||||
error=>{
|
||||
this.pingStatus = false;
|
||||
this.translateService.get('DESTINATION.TEST_CONNECTION_FAILURE').subscribe(res=>this.pingTestMessage=res);
|
||||
this.testOngoing = !this.testOngoing;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
changedTargetName($event: any) {
|
||||
if(this.editable) {
|
||||
this.targetNameHasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
clearPassword($event: any) {
|
||||
if(this.editable) {
|
||||
this.target.password = '';
|
||||
this.endpointHasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
switch(this.actionType) {
|
||||
case ActionType.ADD_NEW:
|
||||
toPromise<number>(this.endpointService
|
||||
.createEndpoint(this.target))
|
||||
.then(
|
||||
response=>{
|
||||
this.errorHandler.info('DESTINATION.CREATED_SUCCESS');
|
||||
this.createEditDestinationOpened = false;
|
||||
this.reload.emit(true);
|
||||
})
|
||||
.catch(
|
||||
error=>{
|
||||
let errorMessageKey = '';
|
||||
switch(error.status) {
|
||||
case 409:
|
||||
errorMessageKey = 'DESTINATION.CONFLICT_NAME';
|
||||
break;
|
||||
case 400:
|
||||
errorMessageKey = 'DESTINATION.INVALID_NAME';
|
||||
break;
|
||||
default:
|
||||
errorMessageKey = 'UNKNOWN_ERROR';
|
||||
}
|
||||
|
||||
this.translateService
|
||||
.get(errorMessageKey)
|
||||
.subscribe(res=>{
|
||||
// if(this.messageHandlerService.isAppLevel(error)) {
|
||||
// this.messageHandlerService.handleError(error);
|
||||
// this.createEditDestinationOpened = false;
|
||||
// } else {
|
||||
// this.inlineAlert.showInlineError(res);
|
||||
// }
|
||||
this.errorHandler.error(res);
|
||||
});
|
||||
}
|
||||
);
|
||||
break;
|
||||
case ActionType.EDIT:
|
||||
if(!(this.targetNameHasChanged || this.endpointHasChanged)) {
|
||||
this.createEditDestinationOpened = false;
|
||||
return;
|
||||
}
|
||||
let payload: Endpoint = Object.assign({}, this.initEndpoint);
|
||||
if(this.targetNameHasChanged) {
|
||||
payload.name = this.target.name;
|
||||
}
|
||||
if (this.endpointHasChanged) {
|
||||
payload.endpoint = this.target.endpoint;
|
||||
payload.username = this.target.username;
|
||||
payload.password = this.target.password;
|
||||
delete payload.name;
|
||||
}
|
||||
toPromise<number>(this.endpointService
|
||||
.updateEndpoint(this.target.id, payload))
|
||||
.then(
|
||||
response=>{
|
||||
this.errorHandler.info('DESTINATION.UPDATED_SUCCESS');
|
||||
this.createEditDestinationOpened = false;
|
||||
this.reload.emit(true);
|
||||
})
|
||||
.catch(
|
||||
error=>{
|
||||
let errorMessageKey = '';
|
||||
switch(error.status) {
|
||||
case 409:this
|
||||
errorMessageKey = 'DESTINATION.CONFLICT_NAME';
|
||||
break;
|
||||
case 400:
|
||||
errorMessageKey = 'DESTINATION.INVALID_NAME';
|
||||
break;
|
||||
default:
|
||||
errorMessageKey = 'UNKNOWN_ERROR';
|
||||
}
|
||||
this.translateService
|
||||
.get(errorMessageKey)
|
||||
.subscribe(res=>{
|
||||
// if(this.messageHandlerService.isAppLevel(error)) {
|
||||
// this.messageHandlerService.handleError(error);
|
||||
// this.createEditDestinationOpened = false;
|
||||
// } else {
|
||||
// this.inlineAlert.showInlineError(res);
|
||||
// }
|
||||
this.errorHandler.error(res);
|
||||
});
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
if(this.hasChanged) {
|
||||
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
||||
} else {
|
||||
this.createEditDestinationOpened = false;
|
||||
if(this.targetForm)
|
||||
this.targetForm.reset();
|
||||
}
|
||||
}
|
||||
|
||||
confirmCancel(confirmed: boolean) {
|
||||
this.createEditDestinationOpened = false;
|
||||
this.inlineAlert.close();
|
||||
}
|
||||
|
||||
ngAfterViewChecked(): void {
|
||||
this.targetForm = this.currentForm;
|
||||
if(this.targetForm) {
|
||||
let comparison: {[key: string]: string} = {
|
||||
targetName: this.initVal.name,
|
||||
endpointUrl: this.initVal.endpoint,
|
||||
username: this.initVal.username,
|
||||
password: this.initVal.password
|
||||
};
|
||||
this.targetForm.valueChanges.subscribe(data=>{
|
||||
for(let key in data) {
|
||||
let current = data[key];
|
||||
let origin: string = comparison[key];
|
||||
if(((this.actionType === ActionType.EDIT && this.editable && !current) || current) &&
|
||||
current !== origin) {
|
||||
this.hasChanged = true;
|
||||
break;
|
||||
} else {
|
||||
this.hasChanged = false;
|
||||
this.inlineAlert.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
7
src/ui_ng/lib/src/create-edit-endpoint/index.ts
Normal file
7
src/ui_ng/lib/src/create-edit-endpoint/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Type } from '@angular/core';
|
||||
import { CreateEditEndpointComponent } from './create-edit-endpoint.component';
|
||||
|
||||
|
||||
export const CREATE_EDIT_ENDPOINT_DIRECTIVES: Type<any>[] = [
|
||||
CreateEditEndpointComponent
|
||||
];
|
10
src/ui_ng/lib/src/endpoint/endpoint.component.css.ts
Normal file
10
src/ui_ng/lib/src/endpoint/endpoint.component.css.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export const ENDPOINT_STYLE: string = `
|
||||
.option-left {
|
||||
padding-left: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 36px;
|
||||
}
|
||||
`;
|
36
src/ui_ng/lib/src/endpoint/endpoint.component.html.ts
Normal file
36
src/ui_ng/lib/src/endpoint/endpoint.component.html.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export const ENDPOINT_TEMPLATE: string = `
|
||||
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)" (cancelAction)="cancelDeletion($event)"></confirmation-dialog>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="flex-items-xs-middle option-left">
|
||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'DESTINATION.ENDPOINT' | translate}}</button>
|
||||
<create-edit-endpoint (reload)="reload($event)"></create-edit-endpoint>
|
||||
</div>
|
||||
<div class="flex-items-xs-middle option-right">
|
||||
<hbr-filter filterPlaceholder='{{"REPLICATION.FILTER_TARGETS_PLACEHOLDER" | translate}}' (filter)="doSearchTargets($event)" [currentValue]="targetName"></hbr-filter>
|
||||
<a href="javascript:void(0)" (click)="refreshTargets()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'DESTINATION.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DESTINATION.URL' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'>
|
||||
<clr-dg-action-overflow>
|
||||
<button class="action-item" (click)="editTarget(t)">{{'DESTINATION.TITLE_EDIT' | translate}}</button>
|
||||
<button class="action-item" (click)="deleteTarget(t)">{{'DESTINATION.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{t.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.endpoint}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (targets ? targets.length : 0) }} {{'DESTINATION.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
133
src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts
Normal file
133
src/ui_ng/lib/src/endpoint/endpoint.component.spec.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { EndpointComponent } from './endpoint.component';
|
||||
import { FilterComponent } from '../filter/filter.component';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.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('EndpointComponent (inline template)', () => {
|
||||
|
||||
let mockData: Endpoint[] = [
|
||||
{
|
||||
"id": 1,
|
||||
"endpoint": "https://10.117.4.151",
|
||||
"name": "target_01",
|
||||
"username": "admin",
|
||||
"password": "",
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"endpoint": "https://10.117.5.142",
|
||||
"name": "target_02",
|
||||
"username": "AAA",
|
||||
"password": "",
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"endpoint": "https://101.1.11.111",
|
||||
"name": "target_03",
|
||||
"username": "admin",
|
||||
"password": "",
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"endpoint": "http://4.4.4.4",
|
||||
"name": "target_04",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"type": 0
|
||||
}
|
||||
];
|
||||
|
||||
let mockOne: Endpoint = {
|
||||
"id": 1,
|
||||
"endpoint": "https://10.117.4.151",
|
||||
"name": "target_01",
|
||||
"username": "admin",
|
||||
"password": "",
|
||||
"type": 0
|
||||
};
|
||||
|
||||
let comp: EndpointComponent;
|
||||
let fixture: ComponentFixture<EndpointComponent>;
|
||||
let de: DebugElement;
|
||||
let el: HTMLElement;
|
||||
|
||||
let config: IServiceConfig = {
|
||||
systemInfoEndpoint: '/api/endpoints/testing'
|
||||
};
|
||||
|
||||
let endpointService: EndpointService;
|
||||
let spy: jasmine.Spy;
|
||||
let spyOnRules: jasmine.Spy;
|
||||
let spyOne: jasmine.Spy;
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ SharedModule ],
|
||||
declarations: [
|
||||
FilterComponent,
|
||||
ConfirmationDialogComponent,
|
||||
CreateEditEndpointComponent,
|
||||
InlineAlertComponent,
|
||||
EndpointComponent ],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(()=>{
|
||||
fixture = TestBed.createComponent(EndpointComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
endpointService = fixture.debugElement.injector.get(EndpointService);
|
||||
|
||||
spy = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockData));
|
||||
spyOnRules = spyOn(endpointService, 'getEndpointWithReplicationRules').and.returnValue([]);
|
||||
spyOne = spyOn(endpointService, 'getEndpoint').and.returnValue(Promise.resolve(mockOne));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should retrieve endpoint data', () => {
|
||||
fixture.detectChanges();
|
||||
expect(spy.calls.any()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should endpoint be initialized', () => {
|
||||
fixture.detectChanges();
|
||||
expect(config.systemInfoEndpoint).toEqual('/api/endpoints/testing');
|
||||
});
|
||||
|
||||
it('should open create endpoint modal', async(() => {
|
||||
fixture.detectChanges();
|
||||
comp.editTarget(mockOne);
|
||||
fixture.whenStable().then(()=>{
|
||||
fixture.detectChanges();
|
||||
expect(comp.target.name).toEqual('target_01');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should filter endpoints by keyword', async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(()=>{
|
||||
fixture.detectChanges();
|
||||
comp.doSearchTargets('target_02');
|
||||
fixture.detectChanges();
|
||||
expect(comp.targets.length).toEqual(1);
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
181
src/ui_ng/lib/src/endpoint/endpoint.component.ts
Normal file
181
src/ui_ng/lib/src/endpoint/endpoint.component.ts
Normal file
@ -0,0 +1,181 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, OnInit, ViewChild, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { Endpoint, ReplicationRule } from '../service/interface';
|
||||
import { EndpointService } from '../service/endpoint.service';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
|
||||
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
||||
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
|
||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../shared/shared.const';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { CreateEditEndpointComponent } from '../create-edit-endpoint/create-edit-endpoint.component';
|
||||
|
||||
import { ENDPOINT_STYLE } from './endpoint.component.css';
|
||||
import { ENDPOINT_TEMPLATE } from './endpoint.component.html';
|
||||
|
||||
import { toPromise } from '../utils';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-endpoint',
|
||||
template: ENDPOINT_TEMPLATE,
|
||||
styles: [ ENDPOINT_STYLE ],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class EndpointComponent implements OnInit {
|
||||
|
||||
@ViewChild(CreateEditEndpointComponent)
|
||||
createEditEndpointComponent: CreateEditEndpointComponent;
|
||||
|
||||
|
||||
@ViewChild('confirmationDialog')
|
||||
confirmationDialogComponent: ConfirmationDialogComponent;
|
||||
|
||||
targets: Endpoint[];
|
||||
target: Endpoint;
|
||||
|
||||
targetName: string;
|
||||
subscription: Subscription;
|
||||
|
||||
get initEndpoint(): Endpoint {
|
||||
return {
|
||||
endpoint: "",
|
||||
name: "",
|
||||
username: "",
|
||||
password: "",
|
||||
type: 0
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private endpointService: EndpointService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private ref: ChangeDetectorRef) {
|
||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
||||
setTimeout(()=>clearInterval(hnd), 1000);
|
||||
}
|
||||
|
||||
confirmDeletion(message: ConfirmationAcknowledgement) {
|
||||
if (message &&
|
||||
message.source === ConfirmationTargets.TARGET &&
|
||||
message.state === ConfirmationState.CONFIRMED) {
|
||||
|
||||
let targetId = message.data;
|
||||
toPromise<number>(this.endpointService
|
||||
.deleteEndpoint(targetId))
|
||||
.then(
|
||||
response => {
|
||||
this.errorHandler.error('DESTINATION.DELETED_SUCCESS');
|
||||
this.reload(true);
|
||||
}).catch(
|
||||
error => {
|
||||
if(error && error.status === 412) {
|
||||
this.errorHandler.error('DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED');
|
||||
} else {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cancelDeletion(message: ConfirmationAcknowledgement) {
|
||||
console.log('Received message from cancelAction:' + JSON.stringify(message));
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.targetName = '';
|
||||
this.retrieve('');
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
retrieve(targetName: string): void {
|
||||
toPromise<Endpoint[]>(this.endpointService
|
||||
.getEndpoints(targetName))
|
||||
.then(
|
||||
targets => {
|
||||
this.targets = targets || [];
|
||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
||||
setTimeout(()=>clearInterval(hnd), 1000);
|
||||
}).catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
doSearchTargets(targetName: string) {
|
||||
this.targetName = targetName;
|
||||
this.retrieve(targetName);
|
||||
}
|
||||
|
||||
refreshTargets() {
|
||||
this.retrieve('');
|
||||
}
|
||||
|
||||
reload($event: any) {
|
||||
this.targetName = '';
|
||||
this.retrieve('');
|
||||
}
|
||||
|
||||
openModal() {
|
||||
this.createEditEndpointComponent.openCreateEditTarget(true);
|
||||
this.target = this.initEndpoint;
|
||||
}
|
||||
|
||||
editTarget(target: Endpoint) {
|
||||
if (target) {
|
||||
let editable = true;
|
||||
|
||||
toPromise<ReplicationRule[]>(this.endpointService
|
||||
.getEndpointWithReplicationRules(target.id))
|
||||
.then(
|
||||
rules=>{
|
||||
if(rules && rules.length > 0) {
|
||||
for(let i = 0; i < rules.length; i++){
|
||||
let p: ReplicationRule = rules[i];
|
||||
if(p.enabled === 1) {
|
||||
editable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.createEditEndpointComponent.openCreateEditTarget(editable, +target.id);
|
||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
||||
setTimeout(()=>clearInterval(hnd), 1000);
|
||||
})
|
||||
.catch(error=>this.errorHandler.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
deleteTarget(target: Endpoint) {
|
||||
console.log('Endpoint:' + JSON.stringify(target));
|
||||
if (target) {
|
||||
let targetId = target.id;
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
'REPLICATION.DELETION_TITLE_TARGET',
|
||||
'REPLICATION.DELETION_SUMMARY_TARGET',
|
||||
target.name,
|
||||
target.id,
|
||||
ConfirmationTargets.TARGET,
|
||||
ConfirmationButtons.DELETE_CANCEL);
|
||||
this.confirmationDialogComponent.open(deletionMessage);
|
||||
}
|
||||
}
|
||||
}
|
7
src/ui_ng/lib/src/endpoint/index.ts
Normal file
7
src/ui_ng/lib/src/endpoint/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Type } from '@angular/core';
|
||||
import { EndpointComponent } from './endpoint.component';
|
||||
|
||||
|
||||
export const ENDPOINT_DIRECTIVES: Type<any>[] = [
|
||||
EndpointComponent
|
||||
];
|
@ -2,7 +2,14 @@ import { NgModule, ModuleWithProviders, Provider, APP_INITIALIZER, Inject } from
|
||||
|
||||
import { LOG_DIRECTIVES } from './log/index';
|
||||
import { FILTER_DIRECTIVES } from './filter/index';
|
||||
import { ENDPOINT_DIRECTIVES } from './endpoint/index';
|
||||
import { CREATE_EDIT_ENDPOINT_DIRECTIVES } from './create-edit-endpoint/index';
|
||||
|
||||
import { SERVICE_CONFIG, IServiceConfig } from './service.config';
|
||||
|
||||
import { CONFIRMATION_DIALOG_DIRECTIVES } from './confirmation-dialog/index';
|
||||
import { INLINE_ALERT_DIRECTIVES } from './inline-alert/index';
|
||||
|
||||
import {
|
||||
AccessLogService,
|
||||
AccessLogDefaultService,
|
||||
@ -110,12 +117,21 @@ export function initConfig(translateService: TranslateService, config: IServiceC
|
||||
],
|
||||
declarations: [
|
||||
LOG_DIRECTIVES,
|
||||
FILTER_DIRECTIVES
|
||||
FILTER_DIRECTIVES,
|
||||
ENDPOINT_DIRECTIVES,
|
||||
CREATE_EDIT_ENDPOINT_DIRECTIVES,
|
||||
CONFIRMATION_DIALOG_DIRECTIVES,
|
||||
INLINE_ALERT_DIRECTIVES
|
||||
],
|
||||
exports: [
|
||||
LOG_DIRECTIVES,
|
||||
FILTER_DIRECTIVES
|
||||
]
|
||||
FILTER_DIRECTIVES,
|
||||
ENDPOINT_DIRECTIVES,
|
||||
CREATE_EDIT_ENDPOINT_DIRECTIVES,
|
||||
CONFIRMATION_DIALOG_DIRECTIVES,
|
||||
INLINE_ALERT_DIRECTIVES
|
||||
],
|
||||
providers: []
|
||||
})
|
||||
|
||||
export class HarborLibraryModule {
|
||||
|
@ -5,3 +5,4 @@ export * from './error-handler/index';
|
||||
//export * from './utils';
|
||||
export * from './log/index';
|
||||
export * from './filter/index';
|
||||
export * from './endpoint/index';
|
7
src/ui_ng/lib/src/inline-alert/index.ts
Normal file
7
src/ui_ng/lib/src/inline-alert/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
import { InlineAlertComponent } from './inline-alert.component';
|
||||
|
||||
export const INLINE_ALERT_DIRECTIVES: Type<any>[] = [
|
||||
InlineAlertComponent
|
||||
];
|
10
src/ui_ng/lib/src/inline-alert/inline-alert.component.css.ts
Normal file
10
src/ui_ng/lib/src/inline-alert/inline-alert.component.css.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export const INLINE_ALERT_STYLE: string = `
|
||||
.alert-text-blink {
|
||||
color: red;
|
||||
font-weight: bolder;
|
||||
}
|
||||
.alert-btn-link {
|
||||
padding: 0px !important;
|
||||
min-width: 30px !important;
|
||||
}
|
||||
`;
|
@ -0,0 +1,13 @@
|
||||
export const INLINE_ALERT_TEMPLATE: string = `
|
||||
<clr-alert [clrAlertType]="inlineAlertType" [clrAlertClosable]="inlineAlertClosable" [(clrAlertClosed)]="alertClose" [clrAlertAppLevel]="useAppLevelStyle">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text" [class.alert-text-blink]="blinking">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
<div class="alert-actions" *ngIf="showCancelAction">
|
||||
<button class="btn btn-sm btn-link alert-btn-link" (click)="close()">{{'BUTTON.NO' | translate}}</button>
|
||||
<button class="btn btn-sm btn-link alert-btn-link" (click)="confirmCancel()">{{'BUTTON.YES' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</clr-alert>
|
||||
`;
|
99
src/ui_ng/lib/src/inline-alert/inline-alert.component.ts
Normal file
99
src/ui_ng/lib/src/inline-alert/inline-alert.component.ts
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { errorHandler } from '../shared/shared.utils';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Subscription } from "rxjs";
|
||||
|
||||
import { INLINE_ALERT_STYLE } from './inline-alert.component.css';
|
||||
import { INLINE_ALERT_TEMPLATE } from './inline-alert.component.html';
|
||||
|
||||
@Component({
|
||||
selector: 'inline-alert',
|
||||
template: INLINE_ALERT_TEMPLATE,
|
||||
styles: [ INLINE_ALERT_STYLE ]
|
||||
})
|
||||
export class InlineAlertComponent {
|
||||
inlineAlertType: string = 'alert-danger';
|
||||
inlineAlertClosable: boolean = false;
|
||||
alertClose: boolean = true;
|
||||
displayedText: string = "";
|
||||
showCancelAction: boolean = false;
|
||||
useAppLevelStyle: boolean = false;
|
||||
timer: Subscription | null = null;
|
||||
count: number = 0;
|
||||
blinking: boolean = false;
|
||||
|
||||
@Output() confirmEvt = new EventEmitter<boolean>();
|
||||
|
||||
constructor(private translate: TranslateService) { }
|
||||
|
||||
public get errorMessage(): string {
|
||||
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);
|
||||
}
|
||||
|
||||
this.inlineAlertType = 'alert-danger';
|
||||
this.showCancelAction = false;
|
||||
this.inlineAlertClosable = true;
|
||||
this.alertClose = false;
|
||||
this.useAppLevelStyle = false;
|
||||
}
|
||||
|
||||
//Show confirmation info with action button
|
||||
public showInlineConfirmation(warning: any): void {
|
||||
this.displayedText = "";
|
||||
if (warning && warning.message) {
|
||||
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 inline sccess info
|
||||
public showInlineSuccess(info: any): void {
|
||||
this.displayedText = "";
|
||||
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 close(): void {
|
||||
this.alertClose = true;
|
||||
}
|
||||
|
||||
public blink() {
|
||||
}
|
||||
|
||||
confirmCancel(): void {
|
||||
this.confirmEvt.emit(true);
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { EndpointService, EndpointDefaultService } from './endpoint.service';
|
||||
|
||||
describe('EndpointService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
providers: [
|
||||
EndpointDefaultService,
|
||||
{
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { RequestQueryParams } from './RequestQueryParams';
|
||||
import { Endpoint } from './interface';
|
||||
import { Endpoint, ReplicationRule } from './interface';
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Http } from '@angular/http';
|
||||
import 'rxjs/add/observable/of';
|
||||
|
||||
/**
|
||||
@ -80,6 +81,15 @@ export abstract class EndpointService {
|
||||
* @memberOf EndpointService
|
||||
*/
|
||||
abstract pingEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any;
|
||||
|
||||
/**
|
||||
* Check endpoint whether in used with specific replication rule.
|
||||
*
|
||||
* @abstract
|
||||
* @param {{number | string}} endpointId
|
||||
* @returns {{Observable<any> | any}}
|
||||
*/
|
||||
abstract getEndpointWithReplicationRules(endpointId: number | string): Observable<any> | Promise<any> | any;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,28 +101,71 @@ export abstract class EndpointService {
|
||||
*/
|
||||
@Injectable()
|
||||
export class EndpointDefaultService extends EndpointService {
|
||||
|
||||
constructor(private http: Http){
|
||||
super();
|
||||
}
|
||||
|
||||
public getEndpoints(endpointName?: string, queryParams?: RequestQueryParams): Observable<Endpoint[]> | Promise<Endpoint[]> | Endpoint[] {
|
||||
return Observable.of([]);
|
||||
return this.http
|
||||
.get(`/api/targets?name=${endpointName}`)
|
||||
.toPromise()
|
||||
.then(response=>response.json())
|
||||
.catch(error=>Promise.reject(error));
|
||||
}
|
||||
|
||||
public getEndpoint(endpointId: number | string): Observable<Endpoint> | Promise<Endpoint> | Endpoint {
|
||||
return Observable.of({});
|
||||
return this.http
|
||||
.get(`/api/targets/${endpointId}`)
|
||||
.toPromise()
|
||||
.then(response=>response.json() as Endpoint)
|
||||
.catch(error=>Promise.reject(error));
|
||||
}
|
||||
|
||||
public createEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any {
|
||||
return Observable.of({});
|
||||
return this.http
|
||||
.post(`/api/targets`, JSON.stringify(endpoint))
|
||||
.toPromise()
|
||||
.then(response=>response.status)
|
||||
.catch(error=>Promise.reject(error));
|
||||
}
|
||||
|
||||
public updateEndpoint(endpointId: number | string, endpoint: Endpoint): Observable<any> | Promise<any> | any {
|
||||
return Observable.of({});
|
||||
return this.http
|
||||
.put(`/api/targets/${endpointId}`, JSON.stringify(endpoint))
|
||||
.toPromise()
|
||||
.then(response=>response.status)
|
||||
.catch(error=>Promise.reject(error));
|
||||
}
|
||||
|
||||
public deleteEndpoint(endpointId: number | string): Observable<any> | Promise<any> | any {
|
||||
return Observable.of({});
|
||||
return this.http
|
||||
.delete(`/api/targets/${endpointId}`)
|
||||
.toPromise()
|
||||
.then(response=>response.status)
|
||||
.catch(error=>Promise.reject(error));
|
||||
}
|
||||
|
||||
public pingEndpoint(endpoint: Endpoint): Observable<any> | Promise<any> | any {
|
||||
return Observable.of({});
|
||||
if(endpoint.id) {
|
||||
return this.http
|
||||
.post(`/api/targets/${endpoint.id}/ping`, {})
|
||||
.toPromise()
|
||||
.then(response=>response.status)
|
||||
.catch(error=>Promise.reject(error));
|
||||
}
|
||||
return this.http
|
||||
.post(`/api/targets/ping`, endpoint)
|
||||
.toPromise()
|
||||
.then(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
public getEndpointWithReplicationRules(endpointId: number | string): Observable<any> | Promise<any> | any {
|
||||
return this.http
|
||||
.get(`/api/targets/${endpointId}/policies`)
|
||||
.toPromise()
|
||||
.then(response=>response.json() as ReplicationRule[])
|
||||
.catch(error=>Promise.reject(error));
|
||||
}
|
||||
}
|
@ -72,7 +72,13 @@ export interface Tag extends Base {
|
||||
* @interface Endpoint
|
||||
* @extends {Base}
|
||||
*/
|
||||
export interface Endpoint extends Base { }
|
||||
export interface Endpoint extends Base {
|
||||
endpoint: string;
|
||||
name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for replication rule.
|
||||
|
@ -114,7 +114,6 @@ export abstract class ReplicationService {
|
||||
* @memberOf ReplicationService
|
||||
*/
|
||||
abstract getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob[]> | Promise<ReplicationJob[]> | ReplicationJob[];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
68
src/ui_ng/lib/src/shared/shared.const.ts
Normal file
68
src/ui_ng/lib/src/shared/shared.const.ts
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
export const supportedLangs = ['en-us', 'zh-cn', 'es-es'];
|
||||
export const enLang = "en-us";
|
||||
export const languageNames = {
|
||||
"en-us": "English",
|
||||
"zh-cn": "中文简体",
|
||||
"es-es": "Español"
|
||||
};
|
||||
export const enum AlertType {
|
||||
DANGER, WARNING, INFO, SUCCESS
|
||||
};
|
||||
|
||||
export const dismissInterval = 10 * 1000;
|
||||
export const httpStatusCode = {
|
||||
"Unauthorized": 401,
|
||||
"Forbidden": 403
|
||||
};
|
||||
export const enum ConfirmationTargets {
|
||||
EMPTY,
|
||||
PROJECT,
|
||||
PROJECT_MEMBER,
|
||||
USER,
|
||||
POLICY,
|
||||
TOGGLE_CONFIRM,
|
||||
TARGET,
|
||||
REPOSITORY,
|
||||
TAG,
|
||||
CONFIG,
|
||||
CONFIG_ROUTE,
|
||||
CONFIG_TAB
|
||||
};
|
||||
|
||||
export const enum ActionType {
|
||||
ADD_NEW, EDIT
|
||||
};
|
||||
|
||||
export const ListMode = {
|
||||
READONLY: "readonly",
|
||||
FULL: "full"
|
||||
};
|
||||
|
||||
export const CommonRoutes = {
|
||||
SIGN_IN: "/sign-in",
|
||||
EMBEDDED_SIGN_IN: "/harbor/sign-in",
|
||||
SIGN_UP: "/sign-in?sign_up=true",
|
||||
EMBEDDED_SIGN_UP: "/harbor/sign-in?sign_up=true",
|
||||
HARBOR_ROOT: "/harbor",
|
||||
HARBOR_DEFAULT: "/harbor/projects"
|
||||
};
|
||||
|
||||
export const enum ConfirmationState {
|
||||
NA, CONFIRMED, CANCEL
|
||||
}
|
||||
export const enum ConfirmationButtons {
|
||||
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpModule, Http } from '@angular/http';
|
||||
import { ClarityModule } from 'clarity-angular';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslateModule, TranslateLoader, TranslateService, MissingTranslationHandler } from "@ngx-translate/core";
|
||||
import { MyMissingTranslationHandler } from '../i18n/missing-trans.handler';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { Http } from '@angular/http';
|
||||
import { TranslatorJsonLoader } from '../i18n/local-json.loader';
|
||||
|
||||
|
||||
/*export function HttpLoaderFactory(http: Http) {
|
||||
return new TranslateHttpLoader(http, 'i18n/lang/', '-lang.json');
|
||||
}*/
|
||||
|
49
src/ui_ng/lib/src/shared/shared.utils.ts
Normal file
49
src/ui_ng/lib/src/shared/shared.utils.ts
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { httpStatusCode, AlertType } from './shared.const';
|
||||
/**
|
||||
* To handle the error message body
|
||||
*
|
||||
* @export
|
||||
* @returns {string}
|
||||
*/
|
||||
export const errorHandler = function (error: any): string {
|
||||
if (!error) {
|
||||
return "UNKNOWN_ERROR";
|
||||
}
|
||||
if (!(error.statusCode || error.status)) {
|
||||
//treat as string message
|
||||
return '' + error;
|
||||
} else {
|
||||
switch (error.statusCode || error.status) {
|
||||
case 400:
|
||||
return "BAD_REQUEST_ERROR";
|
||||
case 401:
|
||||
return "UNAUTHORIZED_ERROR";
|
||||
case 403:
|
||||
return "FORBIDDEN_ERROR";
|
||||
case 404:
|
||||
return "NOT_FOUND_ERROR";
|
||||
case 412:
|
||||
return "PRECONDITION_FAILED";
|
||||
case 409:
|
||||
return "CONFLICT_ERROR";
|
||||
case 500:
|
||||
return "SERVER_ERROR";
|
||||
default:
|
||||
return "UNKNOWN_ERROR";
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user