mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 12:15:20 +01:00
Refactor endpoints and replication management view with harbor-ui library
This commit is contained in:
parent
7cf6510e84
commit
5657005701
@ -40,7 +40,6 @@ export const CREATE_EDIT_ENDPOINT_TEMPLATE: string = `<clr-modal [(clrModalOpen)
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="spin" class="col-md-4"></label>
|
<label for="spin" class="col-md-4"></label>
|
||||||
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
<span class="col-md-8 spinner spinner-inline" [hidden]="!testOngoing"></span>
|
||||||
<span [style.color]="!pingStatus ? 'red': ''" class="form-group-label-override">{{ pingTestMessage }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
@ -11,7 +11,15 @@
|
|||||||
// 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, Output, EventEmitter, ViewChild, AfterViewChecked } from '@angular/core';
|
import {
|
||||||
|
Component,
|
||||||
|
Output,
|
||||||
|
EventEmitter,
|
||||||
|
ViewChild,
|
||||||
|
AfterViewChecked,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef
|
||||||
|
} from '@angular/core';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
import { EndpointService } from '../service/endpoint.service';
|
import { EndpointService } from '../service/endpoint.service';
|
||||||
@ -35,22 +43,19 @@ const FAKE_PASSWORD = 'rjGcfuRu';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'create-edit-endpoint',
|
selector: 'create-edit-endpoint',
|
||||||
template: CREATE_EDIT_ENDPOINT_TEMPLATE,
|
template: CREATE_EDIT_ENDPOINT_TEMPLATE,
|
||||||
styles: [ CREATE_EDIT_ENDPOINT_STYLE ]
|
styles: [CREATE_EDIT_ENDPOINT_STYLE],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class CreateEditEndpointComponent implements AfterViewChecked {
|
export class CreateEditEndpointComponent implements AfterViewChecked {
|
||||||
|
|
||||||
modalTitle: string;
|
modalTitle: string;
|
||||||
createEditDestinationOpened: boolean;
|
createEditDestinationOpened: boolean;
|
||||||
|
|
||||||
editable: boolean;
|
editable: boolean;
|
||||||
|
|
||||||
testOngoing: boolean;
|
testOngoing: boolean;
|
||||||
pingTestMessage: string;
|
|
||||||
pingStatus: boolean;
|
|
||||||
|
|
||||||
actionType: ActionType;
|
actionType: ActionType;
|
||||||
|
|
||||||
target: Endpoint = this.initEndpoint;
|
target: Endpoint = this.initEndpoint;
|
||||||
initVal: Endpoint = this.initEndpoint;
|
initVal: Endpoint = this.initEndpoint;
|
||||||
|
|
||||||
targetForm: NgForm;
|
targetForm: NgForm;
|
||||||
@ -84,7 +89,8 @@ export class CreateEditEndpointComponent implements AfterViewChecked {
|
|||||||
constructor(
|
constructor(
|
||||||
private endpointService: EndpointService,
|
private endpointService: EndpointService,
|
||||||
private errorHandler: ErrorHandler,
|
private errorHandler: ErrorHandler,
|
||||||
private translateService: TranslateService) {}
|
private translateService: TranslateService,
|
||||||
|
private ref: ChangeDetectorRef) { }
|
||||||
|
|
||||||
openCreateEditTarget(editable: boolean, targetId?: number | string) {
|
openCreateEditTarget(editable: boolean, targetId?: number | string) {
|
||||||
|
|
||||||
@ -95,38 +101,32 @@ export class CreateEditEndpointComponent implements AfterViewChecked {
|
|||||||
this.endpointHasChanged = false;
|
this.endpointHasChanged = false;
|
||||||
this.targetNameHasChanged = false;
|
this.targetNameHasChanged = false;
|
||||||
|
|
||||||
this.pingTestMessage = '';
|
|
||||||
this.pingStatus = true;
|
|
||||||
this.testOngoing = false;
|
this.testOngoing = false;
|
||||||
|
|
||||||
if(targetId) {
|
if (targetId) {
|
||||||
this.actionType = ActionType.EDIT;
|
this.actionType = ActionType.EDIT;
|
||||||
this.translateService.get('DESTINATION.TITLE_EDIT').subscribe(res=>this.modalTitle=res);
|
this.translateService.get('DESTINATION.TITLE_EDIT').subscribe(res => this.modalTitle = res);
|
||||||
toPromise<Endpoint>(this.endpointService
|
toPromise<Endpoint>(this.endpointService
|
||||||
.getEndpoint(targetId))
|
.getEndpoint(targetId))
|
||||||
.then(
|
.then(
|
||||||
target=>{
|
target => {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.initVal.name = this.target.name;
|
this.initVal.name = this.target.name;
|
||||||
this.initVal.endpoint = this.target.endpoint;
|
this.initVal.endpoint = this.target.endpoint;
|
||||||
this.initVal.username = this.target.username;
|
this.initVal.username = this.target.username;
|
||||||
this.initVal.password = FAKE_PASSWORD;
|
this.initVal.password = FAKE_PASSWORD;
|
||||||
this.target.password = this.initVal.password;
|
this.target.password = this.initVal.password;
|
||||||
})
|
})
|
||||||
.catch(error=>this.errorHandler.error(error));
|
.catch(error => this.errorHandler.error(error));
|
||||||
} else {
|
} else {
|
||||||
this.actionType = ActionType.ADD_NEW;
|
this.actionType = ActionType.ADD_NEW;
|
||||||
this.translateService.get('DESTINATION.TITLE_ADD').subscribe(res=>this.modalTitle=res);
|
this.translateService.get('DESTINATION.TITLE_ADD').subscribe(res => this.modalTitle = res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testConnection() {
|
testConnection() {
|
||||||
this.translateService.get('DESTINATION.TESTING_CONNECTION').subscribe(res=>this.pingTestMessage=res);
|
|
||||||
this.pingStatus = true;
|
|
||||||
this.testOngoing = !this.testOngoing;
|
|
||||||
|
|
||||||
let payload: Endpoint = this.initEndpoint;
|
let payload: Endpoint = this.initEndpoint;
|
||||||
if(this.endpointHasChanged) {
|
if (this.endpointHasChanged) {
|
||||||
payload.endpoint = this.target.endpoint;
|
payload.endpoint = this.target.endpoint;
|
||||||
payload.username = this.target.username;
|
payload.username = this.target.username;
|
||||||
payload.password = this.target.password;
|
payload.password = this.target.password;
|
||||||
@ -134,42 +134,41 @@ export class CreateEditEndpointComponent implements AfterViewChecked {
|
|||||||
payload.id = this.target.id;
|
payload.id = this.target.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.testOngoing = true;
|
||||||
toPromise<Endpoint>(this.endpointService
|
toPromise<Endpoint>(this.endpointService
|
||||||
.pingEndpoint(payload))
|
.pingEndpoint(payload))
|
||||||
.then(
|
.then(
|
||||||
response=>{
|
response => {
|
||||||
this.pingStatus = true;
|
this.testOngoing = false;
|
||||||
this.translateService.get('DESTINATION.TEST_CONNECTION_SUCCESS').subscribe(res=>this.pingTestMessage=res);
|
this.inlineAlert.showInlineSuccess({ message: "DESTINATION.TEST_CONNECTION_SUCCESS" });
|
||||||
this.testOngoing = !this.testOngoing;
|
}).catch(
|
||||||
}).catch(
|
error => {
|
||||||
error=>{
|
this.testOngoing = false;
|
||||||
this.pingStatus = false;
|
this.inlineAlert.showInlineError('DESTINATION.TEST_CONNECTION_FAILURE');
|
||||||
this.translateService.get('DESTINATION.TEST_CONNECTION_FAILURE').subscribe(res=>this.pingTestMessage=res);
|
});
|
||||||
this.testOngoing = !this.testOngoing;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changedTargetName($event: any) {
|
changedTargetName($event: any) {
|
||||||
if(this.editable) {
|
if (this.editable) {
|
||||||
this.targetNameHasChanged = true;
|
this.targetNameHasChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPassword($event: any) {
|
clearPassword($event: any) {
|
||||||
if(this.editable) {
|
if (this.editable) {
|
||||||
this.target.password = '';
|
this.target.password = '';
|
||||||
this.endpointHasChanged = true;
|
this.endpointHasChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
switch(this.actionType) {
|
switch (this.actionType) {
|
||||||
case ActionType.ADD_NEW:
|
case ActionType.ADD_NEW:
|
||||||
this.addEndpoint();
|
this.addEndpoint();
|
||||||
break;
|
break;
|
||||||
case ActionType.EDIT:
|
case ActionType.EDIT:
|
||||||
this.updateEndpoint();
|
this.updateEndpoint();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,31 +176,31 @@ export class CreateEditEndpointComponent implements AfterViewChecked {
|
|||||||
toPromise<number>(this.endpointService
|
toPromise<number>(this.endpointService
|
||||||
.createEndpoint(this.target))
|
.createEndpoint(this.target))
|
||||||
.then(
|
.then(
|
||||||
response=>{
|
response => {
|
||||||
this.translateService.get('DESTINATION.CREATED_SUCCESS')
|
this.translateService.get('DESTINATION.CREATED_SUCCESS')
|
||||||
.subscribe(res=>this.errorHandler.info(res));
|
.subscribe(res => this.errorHandler.info(res));
|
||||||
this.createEditDestinationOpened = false;
|
this.createEditDestinationOpened = false;
|
||||||
this.reload.emit(true);
|
this.reload.emit(true);
|
||||||
})
|
})
|
||||||
.catch(
|
.catch(
|
||||||
error=>{
|
error => {
|
||||||
let errorMessageKey = this.handleErrorMessageKey(error.status);
|
let errorMessageKey = this.handleErrorMessageKey(error.status);
|
||||||
this.translateService
|
this.translateService
|
||||||
.get(errorMessageKey)
|
.get(errorMessageKey)
|
||||||
.subscribe(res=>{
|
.subscribe(res => {
|
||||||
this.errorHandler.error(res);
|
this.inlineAlert.showInlineError(res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEndpoint() {
|
updateEndpoint() {
|
||||||
if(!(this.targetNameHasChanged || this.endpointHasChanged)) {
|
if (!(this.targetNameHasChanged || this.endpointHasChanged)) {
|
||||||
this.createEditDestinationOpened = false;
|
this.createEditDestinationOpened = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let payload: Endpoint = this.initEndpoint;
|
let payload: Endpoint = this.initEndpoint;
|
||||||
if(this.targetNameHasChanged) {
|
if (this.targetNameHasChanged) {
|
||||||
payload.name = this.target.name;
|
payload.name = this.target.name;
|
||||||
delete payload.endpoint;
|
delete payload.endpoint;
|
||||||
}
|
}
|
||||||
@ -212,45 +211,45 @@ export class CreateEditEndpointComponent implements AfterViewChecked {
|
|||||||
delete payload.name;
|
delete payload.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this.target.id) { return; }
|
if (!this.target.id) { return; }
|
||||||
toPromise<number>(this.endpointService
|
toPromise<number>(this.endpointService
|
||||||
.updateEndpoint(this.target.id, payload))
|
.updateEndpoint(this.target.id, payload))
|
||||||
.then(
|
.then(
|
||||||
response=>{
|
response => {
|
||||||
this.translateService.get('DESTINATION.UPDATED_SUCCESS')
|
this.translateService.get('DESTINATION.UPDATED_SUCCESS')
|
||||||
.subscribe(res=>this.errorHandler.info(res));
|
.subscribe(res => this.errorHandler.info(res));
|
||||||
this.createEditDestinationOpened = false;
|
this.createEditDestinationOpened = false;
|
||||||
this.reload.emit(true);
|
this.reload.emit(true);
|
||||||
})
|
})
|
||||||
.catch(
|
.catch(
|
||||||
error=>{
|
error => {
|
||||||
let errorMessageKey = this.handleErrorMessageKey(error.status);
|
let errorMessageKey = this.handleErrorMessageKey(error.status);
|
||||||
this.translateService
|
this.translateService
|
||||||
.get(errorMessageKey)
|
.get(errorMessageKey)
|
||||||
.subscribe(res=>{
|
.subscribe(res => {
|
||||||
this.errorHandler.error(res);
|
this.inlineAlert.showInlineError(res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleErrorMessageKey(status: number): string {
|
handleErrorMessageKey(status: number): string {
|
||||||
switch(status) {
|
switch (status) {
|
||||||
case 409:this
|
case 409: this
|
||||||
return 'DESTINATION.CONFLICT_NAME';
|
return 'DESTINATION.CONFLICT_NAME';
|
||||||
case 400:
|
case 400:
|
||||||
return 'DESTINATION.INVALID_NAME';
|
return 'DESTINATION.INVALID_NAME';
|
||||||
default:
|
default:
|
||||||
return 'UNKNOWN_ERROR';
|
return 'UNKNOWN_ERROR';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancel() {
|
onCancel() {
|
||||||
if(this.hasChanged) {
|
if (this.hasChanged) {
|
||||||
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' });
|
||||||
} else {
|
} else {
|
||||||
this.createEditDestinationOpened = false;
|
this.createEditDestinationOpened = false;
|
||||||
if(this.targetForm)
|
if (this.targetForm)
|
||||||
this.targetForm.reset();
|
this.targetForm.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,20 +261,20 @@ export class CreateEditEndpointComponent implements AfterViewChecked {
|
|||||||
|
|
||||||
ngAfterViewChecked(): void {
|
ngAfterViewChecked(): void {
|
||||||
this.targetForm = this.currentForm;
|
this.targetForm = this.currentForm;
|
||||||
if(this.targetForm) {
|
if (this.targetForm) {
|
||||||
let comparison: {[key: string]: any} = {
|
let comparison: { [key: string]: any } = {
|
||||||
targetName: this.initVal.name,
|
targetName: this.initVal.name,
|
||||||
endpointUrl: this.initVal.endpoint,
|
endpointUrl: this.initVal.endpoint,
|
||||||
username: this.initVal.username,
|
username: this.initVal.username,
|
||||||
password: this.initVal.password
|
password: this.initVal.password
|
||||||
};
|
};
|
||||||
let self: CreateEditEndpointComponent | any = this;
|
let self: CreateEditEndpointComponent | any = this;
|
||||||
if(self) {
|
if (self) {
|
||||||
self.targetForm.valueChanges.subscribe((data: any)=>{
|
self.targetForm.valueChanges.subscribe((data: any) => {
|
||||||
for(let key in data) {
|
for (let key in data) {
|
||||||
let current = data[key];
|
let current = data[key];
|
||||||
let origin: string = comparison[key];
|
let origin: string = comparison[key];
|
||||||
if(((this.actionType === ActionType.EDIT && this.editable && !current) || current) &&
|
if (((this.actionType === ActionType.EDIT && this.editable && !current) || current) &&
|
||||||
current !== origin) {
|
current !== origin) {
|
||||||
this.hasChanged = true;
|
this.hasChanged = true;
|
||||||
break;
|
break;
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
"clarity-icons": "^0.9.0",
|
"clarity-icons": "^0.9.0",
|
||||||
"clarity-ui": "^0.9.0",
|
"clarity-ui": "^0.9.0",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"harbor-ui": "^0.1.71",
|
"harbor-ui": "^0.1.83",
|
||||||
"intl": "^1.2.5",
|
"intl": "^1.2.5",
|
||||||
"mutationobserver-shim": "^0.3.2",
|
"mutationobserver-shim": "^0.3.2",
|
||||||
"ngx-clipboard": "^8.0.2",
|
"ngx-clipboard": "^8.0.2",
|
||||||
|
@ -19,20 +19,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { ClarityModule } from 'clarity-angular';
|
import { ClarityModule } from 'clarity-angular';
|
||||||
import { CookieModule } from 'ngx-cookie';
|
import { CookieModule } from 'ngx-cookie';
|
||||||
|
|
||||||
import {
|
|
||||||
IServiceConfig,
|
|
||||||
SERVICE_CONFIG,
|
|
||||||
HarborLibraryModule
|
|
||||||
} from 'harbor-ui';
|
|
||||||
|
|
||||||
const uiLibConfig: IServiceConfig = {
|
|
||||||
enablei18Support: true,
|
|
||||||
langCookieKey: "harbor-lang",
|
|
||||||
langMessageLoader: "http",
|
|
||||||
langMessagePathForHttpLoader: "src/i18n/lang/",
|
|
||||||
langMessageFileSuffixForHttpLoader: "-lang.json",
|
|
||||||
};
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -40,18 +26,14 @@ const uiLibConfig: IServiceConfig = {
|
|||||||
HttpModule,
|
HttpModule,
|
||||||
ClarityModule.forRoot(),
|
ClarityModule.forRoot(),
|
||||||
CookieModule.forRoot(),
|
CookieModule.forRoot(),
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule
|
||||||
HarborLibraryModule.forRoot({
|
|
||||||
config: { provide: SERVICE_CONFIG, useValue: uiLibConfig }
|
|
||||||
})
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
HttpModule,
|
HttpModule,
|
||||||
ClarityModule,
|
ClarityModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule
|
||||||
HarborLibraryModule
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreModule {
|
export class CoreModule {
|
||||||
|
@ -21,14 +21,14 @@ import { ProjectComponent } from './project/project.component';
|
|||||||
import { UserComponent } from './user/user.component';
|
import { UserComponent } from './user/user.component';
|
||||||
import { ReplicationManagementComponent } from './replication/replication-management/replication-management.component';
|
import { ReplicationManagementComponent } from './replication/replication-management/replication-management.component';
|
||||||
|
|
||||||
import { TotalReplicationComponent } from './replication/total-replication/total-replication.component';
|
import { TotalReplicationPageComponent } from './replication/total-replication/total-replication-page.component';
|
||||||
import { DestinationComponent } from './replication/destination/destination.component';
|
import { DestinationPageComponent } from './replication/destination/destination-page.component';
|
||||||
|
|
||||||
import { ProjectDetailComponent } from './project/project-detail/project-detail.component';
|
import { ProjectDetailComponent } from './project/project-detail/project-detail.component';
|
||||||
|
|
||||||
import { RepositoryComponent } from './repository/repository.component';
|
import { RepositoryComponent } from './repository/repository.component';
|
||||||
import { TagRepositoryComponent } from './repository/tag-repository/tag-repository.component';
|
import { TagRepositoryComponent } from './repository/tag-repository/tag-repository.component';
|
||||||
import { ReplicationComponent } from './replication/replication.component';
|
import { ReplicationPageComponent } from './replication/replication-page.component';
|
||||||
import { MemberComponent } from './project/member/member.component';
|
import { MemberComponent } from './project/member/member.component';
|
||||||
import { AuditLogComponent } from './log/audit-log.component';
|
import { AuditLogComponent } from './log/audit-log.component';
|
||||||
|
|
||||||
@ -83,11 +83,11 @@ const harborRoutes: Routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'rules',
|
path: 'rules',
|
||||||
component: TotalReplicationComponent
|
component: TotalReplicationPageComponent
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'endpoints',
|
path: 'endpoints',
|
||||||
component: DestinationComponent
|
component: DestinationPageComponent
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -113,7 +113,7 @@ const harborRoutes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'replication',
|
path: 'replication',
|
||||||
component: ReplicationComponent,
|
component: ReplicationPageComponent,
|
||||||
canActivate: [SystemAdminGuard]
|
canActivate: [SystemAdminGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
.form-group-label-override {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
<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>
|
|
@ -1,279 +0,0 @@
|
|||||||
// 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 { ReplicationService } from '../replication.service';
|
|
||||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
|
||||||
import { ActionType } from '../../shared/shared.const';
|
|
||||||
|
|
||||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
|
||||||
|
|
||||||
import { Target } from '../target';
|
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
const FAKE_PASSWORD = 'rjGcfuRu';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'create-edit-destination',
|
|
||||||
templateUrl: './create-edit-destination.component.html',
|
|
||||||
styleUrls: [ 'create-edit-destination.component.css' ]
|
|
||||||
})
|
|
||||||
export class CreateEditDestinationComponent implements AfterViewChecked {
|
|
||||||
|
|
||||||
modalTitle: string;
|
|
||||||
createEditDestinationOpened: boolean;
|
|
||||||
|
|
||||||
editable: boolean;
|
|
||||||
|
|
||||||
testOngoing: boolean;
|
|
||||||
pingTestMessage: string;
|
|
||||||
pingStatus: boolean;
|
|
||||||
|
|
||||||
actionType: ActionType;
|
|
||||||
|
|
||||||
target: Target = new Target();
|
|
||||||
initVal: Target = new Target();
|
|
||||||
|
|
||||||
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>();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private replicationService: ReplicationService,
|
|
||||||
private messageHandlerService: MessageHandlerService,
|
|
||||||
private translateService: TranslateService) {}
|
|
||||||
|
|
||||||
openCreateEditTarget(editable: boolean, targetId?: number) {
|
|
||||||
|
|
||||||
this.target = new Target();
|
|
||||||
this.createEditDestinationOpened = true;
|
|
||||||
this.editable = editable;
|
|
||||||
|
|
||||||
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);
|
|
||||||
this.replicationService
|
|
||||||
.getTarget(targetId)
|
|
||||||
.subscribe(
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
error=>this.messageHandlerService.handleError(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: Target = new Target();
|
|
||||||
if(this.endpointHasChanged) {
|
|
||||||
payload.endpoint = this.target.endpoint;
|
|
||||||
payload.username = this.target.username;
|
|
||||||
payload.password = this.target.password;
|
|
||||||
} else {
|
|
||||||
payload.id = this.target.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.replicationService
|
|
||||||
.pingTarget(payload)
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.pingStatus = true;
|
|
||||||
this.translateService.get('DESTINATION.TEST_CONNECTION_SUCCESS').subscribe(res=>this.pingTestMessage=res);
|
|
||||||
this.testOngoing = !this.testOngoing;
|
|
||||||
},
|
|
||||||
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:
|
|
||||||
this.replicationService
|
|
||||||
.createTarget(this.target)
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.messageHandlerService.showSuccess('DESTINATION.CREATED_SUCCESS');
|
|
||||||
this.createEditDestinationOpened = false;
|
|
||||||
this.reload.emit(true);
|
|
||||||
},
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case ActionType.EDIT:
|
|
||||||
if(!(this.targetNameHasChanged || this.endpointHasChanged)) {
|
|
||||||
this.createEditDestinationOpened = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let payload: Target = new Target();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
this.replicationService
|
|
||||||
.updateTarget(payload, this.target.id)
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.messageHandlerService.showSuccess('DESTINATION.UPDATED_SUCCESS');
|
|
||||||
this.createEditDestinationOpened = false;
|
|
||||||
this.reload.emit(true);
|
|
||||||
},
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onCancel() {
|
|
||||||
if(this.hasChanged) {
|
|
||||||
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
|
||||||
} else {
|
|
||||||
this.createEditDestinationOpened = false;
|
|
||||||
this.targetForm.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmCancel(confirmed: boolean) {
|
|
||||||
this.createEditDestinationOpened = false;
|
|
||||||
this.inlineAlert.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedName: {} = {
|
|
||||||
'targetName': 'name',
|
|
||||||
'endpointUrl': 'endpoint',
|
|
||||||
'username': 'username',
|
|
||||||
'password': 'password'
|
|
||||||
};
|
|
||||||
|
|
||||||
ngAfterViewChecked(): void {
|
|
||||||
this.targetForm = this.currentForm;
|
|
||||||
if(this.targetForm) {
|
|
||||||
this.targetForm.valueChanges.subscribe(data=>{
|
|
||||||
for(let i in data) {
|
|
||||||
let current = data[i];
|
|
||||||
let origin = this.initVal[this.mappedName[i]];
|
|
||||||
if(((this.actionType === ActionType.EDIT && this.editable && !current) || current) && current !== origin) {
|
|
||||||
this.hasChanged = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
this.hasChanged = false;
|
|
||||||
this.inlineAlert.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
<div style="margin-top: 24px;">
|
||||||
|
<hbr-endpoint></hbr-endpoint>
|
||||||
|
</div>
|
@ -11,14 +11,11 @@
|
|||||||
// 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.
|
||||||
export class CreateEditPolicy {
|
import { Component } from '@angular/core';
|
||||||
policyId: number;
|
|
||||||
name: string;
|
@Component({
|
||||||
description: string;
|
selector: 'destination-page',
|
||||||
enable: boolean;
|
templateUrl: 'destination-page.component.html'
|
||||||
targetId: number;
|
})
|
||||||
targetName: string;
|
export class DestinationPageComponent {
|
||||||
endpointUrl: string;
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
}
|
}
|
@ -1,8 +0,0 @@
|
|||||||
.option-left {
|
|
||||||
padding-left: 16px;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
.option-right {
|
|
||||||
padding-right: 16px;
|
|
||||||
margin-top: 36px;
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<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-destination (reload)="reload($event)"></create-edit-destination>
|
|
||||||
</div>
|
|
||||||
<div class="flex-items-xs-middle option-right">
|
|
||||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_TARGETS_PLACEHOLDER" | translate}}' (filter)="doSearchTargets($event)" [currentValue]="targetName"></grid-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>
|
|
@ -1,157 +0,0 @@
|
|||||||
// 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 { Target } from '../target';
|
|
||||||
import { ReplicationService } from '../replication.service';
|
|
||||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
|
||||||
|
|
||||||
import { ConfirmationDialogService } from '../../shared/confirmation-dialog/confirmation-dialog.service';
|
|
||||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
|
||||||
|
|
||||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../../shared/shared.const';
|
|
||||||
|
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
|
|
||||||
import { CreateEditDestinationComponent } from '../create-edit-destination/create-edit-destination.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'destination',
|
|
||||||
templateUrl: 'destination.component.html',
|
|
||||||
styleUrls: ['./destination.component.css'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class DestinationComponent implements OnInit {
|
|
||||||
|
|
||||||
@ViewChild(CreateEditDestinationComponent)
|
|
||||||
createEditDestinationComponent: CreateEditDestinationComponent;
|
|
||||||
|
|
||||||
targets: Target[];
|
|
||||||
target: Target;
|
|
||||||
|
|
||||||
targetName: string;
|
|
||||||
subscription: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private replicationService: ReplicationService,
|
|
||||||
private messageHandlerService: MessageHandlerService,
|
|
||||||
private deletionDialogService: ConfirmationDialogService,
|
|
||||||
private ref: ChangeDetectorRef) {
|
|
||||||
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(message => {
|
|
||||||
if (message &&
|
|
||||||
message.source === ConfirmationTargets.TARGET &&
|
|
||||||
message.state === ConfirmationState.CONFIRMED) {
|
|
||||||
let targetId = message.data;
|
|
||||||
this.replicationService
|
|
||||||
.deleteTarget(targetId)
|
|
||||||
.subscribe(
|
|
||||||
response => {
|
|
||||||
this.messageHandlerService.showSuccess('DESTINATION.DELETED_SUCCESS');
|
|
||||||
this.reload(true);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
if(error && error.status === 412) {
|
|
||||||
this.messageHandlerService.showError('DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED', '');
|
|
||||||
} else {
|
|
||||||
this.messageHandlerService.handleError(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
|
||||||
setTimeout(()=>clearInterval(hnd), 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.targetName = '';
|
|
||||||
this.retrieve('');
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.subscription) {
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieve(targetName: string): void {
|
|
||||||
this.replicationService
|
|
||||||
.listTargets(targetName)
|
|
||||||
.subscribe(
|
|
||||||
targets => {
|
|
||||||
this.targets = targets || [];
|
|
||||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
|
||||||
setTimeout(()=>clearInterval(hnd), 1000);
|
|
||||||
},
|
|
||||||
error => this.messageHandlerService.handleError(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
doSearchTargets(targetName: string) {
|
|
||||||
this.targetName = targetName;
|
|
||||||
this.retrieve(targetName);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshTargets() {
|
|
||||||
this.retrieve('');
|
|
||||||
}
|
|
||||||
|
|
||||||
reload($event: any) {
|
|
||||||
this.targetName = '';
|
|
||||||
this.retrieve('');
|
|
||||||
}
|
|
||||||
|
|
||||||
openModal() {
|
|
||||||
this.createEditDestinationComponent.openCreateEditTarget(true);
|
|
||||||
this.target = new Target();
|
|
||||||
}
|
|
||||||
|
|
||||||
editTarget(target: Target) {
|
|
||||||
if (target) {
|
|
||||||
let editable = true;
|
|
||||||
this.replicationService
|
|
||||||
.listTargetPolicies(target.id)
|
|
||||||
.subscribe(
|
|
||||||
policies=>{
|
|
||||||
if(policies && policies.length > 0) {
|
|
||||||
for(let i = 0; i < policies.length; i++){
|
|
||||||
let p = policies[i];
|
|
||||||
if(p.enabled === 1) {
|
|
||||||
editable = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.createEditDestinationComponent.openCreateEditTarget(editable, target.id);
|
|
||||||
let hnd = setInterval(()=>this.ref.markForCheck(), 100);
|
|
||||||
setTimeout(()=>clearInterval(hnd), 1000);
|
|
||||||
},
|
|
||||||
error=>this.messageHandlerService.handleError(error)
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTarget(target: 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.deletionDialogService.openComfirmDialog(deletionMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
|
||||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.END_TIME' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-row *ngFor="let j of jobs" [clrDgItem]='j'>
|
|
||||||
<clr-dg-cell>{{j.repository}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{j.status}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{j.operation}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{j.creation_time | date: 'short'}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{j.update_time | date: 'short'}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>
|
|
||||||
<a href="/api/jobs/replication/{{j.id}}/log" target="_BLANK">
|
|
||||||
<clr-icon shape="clipboard"></clr-icon>
|
|
||||||
</a>
|
|
||||||
</clr-dg-cell>
|
|
||||||
</clr-dg-row>
|
|
||||||
<clr-dg-footer>
|
|
||||||
{{ totalRecordCount }} {{'REPLICATION.ITEMS' | translate}}
|
|
||||||
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
|
|
||||||
</clr-dg-footer>
|
|
||||||
</clr-datagrid>
|
|
@ -1,44 +0,0 @@
|
|||||||
// 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, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
|
||||||
import { Job } from '../job';
|
|
||||||
import { State } from 'clarity-angular';
|
|
||||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'list-job',
|
|
||||||
templateUrl: 'list-job.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ListJobComponent {
|
|
||||||
@Input() jobs: Job[];
|
|
||||||
@Input() totalRecordCount: number;
|
|
||||||
@Input() totalPage: number;
|
|
||||||
@Output() paginate = new EventEmitter<State>();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private messageHandlerService: MessageHandlerService,
|
|
||||||
private ref: ChangeDetectorRef) {
|
|
||||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
|
||||||
setTimeout(()=>clearInterval(hnd), 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
pageOffset: number = 1;
|
|
||||||
|
|
||||||
refresh(state: State) {
|
|
||||||
if(this.jobs) {
|
|
||||||
this.paginate.emit(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
// 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.
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"project_id": 1,
|
|
||||||
"project_name": "library",
|
|
||||||
"target_id": 1,
|
|
||||||
"target_name": "target_01",
|
|
||||||
"name": "sync_01",
|
|
||||||
"enabled": 0,
|
|
||||||
"description": "sync_01 desc.",
|
|
||||||
"cron_str": "",
|
|
||||||
"start_time": "0001-01-01T00:00:00Z",
|
|
||||||
"creation_time": "2017-02-24T06:41:52Z",
|
|
||||||
"update_time": "2017-02-24T06:41:52Z",
|
|
||||||
"error_job_count": 0,
|
|
||||||
"deleted": 0
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class Policy {
|
|
||||||
id: number;
|
|
||||||
project_id: number;
|
|
||||||
project_name: string;
|
|
||||||
target_id: number;
|
|
||||||
target_name: string;
|
|
||||||
name: string;
|
|
||||||
enabled: number;
|
|
||||||
description: string;
|
|
||||||
cron_str: string;
|
|
||||||
start_time: Date;
|
|
||||||
creation_time: Date;
|
|
||||||
update_time: Date;
|
|
||||||
error_job_count: number;
|
|
||||||
deleted: number;
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
<div style="margin-top: 24px;">
|
||||||
|
<hbr-replication [projectId]="projectIdentify" [withReplicationJob]='true'></hbr-replication>
|
||||||
|
</div>
|
@ -11,26 +11,19 @@
|
|||||||
// 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 } from '@angular/core';
|
||||||
{
|
import { ActivatedRoute } from '@angular/router';
|
||||||
"id": 1,
|
|
||||||
"endpoint": "http://10.117.4.151",
|
|
||||||
"name": "target_01",
|
|
||||||
"username": "admin",
|
|
||||||
"password": "Harbor12345",
|
|
||||||
"type": 0,
|
|
||||||
"creation_time": "2017-02-24T06:41:52Z",
|
|
||||||
"update_time": "2017-02-24T06:41:52Z"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class Target {
|
@Component({
|
||||||
id: number;
|
selector: 'replicaton',
|
||||||
endpoint: string;
|
templateUrl: 'replication-page.component.html'
|
||||||
name: string;
|
})
|
||||||
username: string;
|
export class ReplicationPageComponent implements OnInit {
|
||||||
password: string;
|
projectIdentify: string | number;
|
||||||
type: number;
|
|
||||||
creation_time: Date;
|
constructor(private route: ActivatedRoute) { }
|
||||||
update_time: Date;
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.projectIdentify = +this.route.snapshot.parent.params['id'];
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,17 +0,0 @@
|
|||||||
.option-left {
|
|
||||||
padding-left: 16px;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
.option-right {
|
|
||||||
padding-right: 16px;
|
|
||||||
margin-top: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-left-down {
|
|
||||||
margin-top: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-right-down {
|
|
||||||
padding-right: 16px;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
<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-xs-middle option-left">
|
|
||||||
<button class="btn btn-link" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'REPLICATION.REPLICATION_RULE' | translate}}</button>
|
|
||||||
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
|
||||||
</div>
|
|
||||||
<div class="flex-xs-middle option-right">
|
|
||||||
<div class="select" style="float: left;">
|
|
||||||
<select (change)="doFilterPolicyStatus($event)">
|
|
||||||
<option *ngFor="let r of ruleStatus" value="{{r.key}}">{{r.description | translate}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filter)="doSearchPolicies($event)" [currentValue]="search.policyName"></grid-filter>
|
|
||||||
<a href="javascript:void(0)" (click)="refreshPolicies()">
|
|
||||||
<clr-icon shape="refresh"></clr-icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
|
||||||
<list-policy [policies]="changedPolicies" [projectless]="false" [selectedId]="initSelectedId" (selectOne)="selectOnePolicy($event)" (editOne)="openEditPolicy($event)" (reload)="reloadPolicies($event)"></list-policy>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
|
||||||
<div class="row flex-items-xs-between">
|
|
||||||
<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">
|
|
||||||
<button class="btn btn-link" (click)="toggleSearchJobOptionalName(currentJobSearchOption)">{{toggleJobSearchOption[currentJobSearchOption] | translate}}</button>
|
|
||||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_JOBS_PLACEHOLDER" | translate}}' (filter)="doSearchJobs($event)" [currentValue]="search.repoName" ></grid-filter>
|
|
||||||
<a href="javascript:void(0)" (click)="refreshJobs()">
|
|
||||||
<clr-icon shape="refresh"></clr-icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row flex-items-xs-right option-right" [hidden]="currentJobSearchOption === 0">
|
|
||||||
<div class="select" style="float: left;">
|
|
||||||
<select (change)="doFilterJobStatus($event)">
|
|
||||||
<option *ngFor="let j of jobStatus" value="{{j.key}}" [selected]="currentJobStatus.key === j.key">{{j.description | translate}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="flex-items-xs-middle">
|
|
||||||
<clr-icon shape="date"></clr-icon>
|
|
||||||
<label for="fromDateInput" aria-haspopup="true" role="tooltip" [class.invalid]="fromTimeInvalid" class="tooltip tooltip-validation invalid tooltip-sm">
|
|
||||||
<input id="fromDateInput" type="date" #fromTime="ngModel" name="from" [(ngModel)]="search.startTime" dateValidator placeholder="dd/mm/yyyy" (change)="doJobSearchByStartTime(fromTime.value)">
|
|
||||||
<span *ngIf="fromTimeInvalid" class="tooltip-content">
|
|
||||||
{{'AUDIT_LOG.INVALID_DATE' | translate }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<clr-icon shape="date"></clr-icon>
|
|
||||||
<label for="toDateInput" aria-haspopup="true" role="tooltip" [class.invalid]="toTimeInvalid" class="tooltip tooltip-validation invalid tooltip-sm">
|
|
||||||
<input id="toDateInput" type="date" #toTime="ngModel" name="to" [(ngModel)]="search.endTime" dateValidator placeholder="dd/mm/yyyy" (change)="doJobSearchByEndTime(toTime.value)">
|
|
||||||
<span *ngIf="toTimeInvalid" class="tooltip-content">
|
|
||||||
{{'AUDIT_LOG.INVALID_DATE' | translate }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
|
||||||
<list-job [jobs]="changedJobs" [totalPage]="jobsTotalPage" [totalRecordCount]="jobsTotalRecordCount" (paginate)="fetchPolicyJobs($event)"></list-job>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,274 +0,0 @@
|
|||||||
// 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 } from '@angular/core';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { NgModel } from '@angular/forms';
|
|
||||||
|
|
||||||
import { CreateEditPolicyComponent } from '../shared/create-edit-policy/create-edit-policy.component';
|
|
||||||
|
|
||||||
import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
|
|
||||||
|
|
||||||
import { ReplicationService } from './replication.service';
|
|
||||||
|
|
||||||
import { SessionUser } from '../shared/session-user';
|
|
||||||
import { Policy } from './policy';
|
|
||||||
import { Job } from './job';
|
|
||||||
import { Target } from './target';
|
|
||||||
|
|
||||||
import { State } from 'clarity-angular';
|
|
||||||
|
|
||||||
const ruleStatus = [
|
|
||||||
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS'},
|
|
||||||
{ 'key': '1', 'description': 'REPLICATION.ENABLED'},
|
|
||||||
{ 'key': '0', 'description': 'REPLICATION.DISABLED'}
|
|
||||||
];
|
|
||||||
|
|
||||||
const jobStatus = [
|
|
||||||
{ 'key': 'all', 'description': 'REPLICATION.ALL' },
|
|
||||||
{ 'key': 'pending', 'description': 'REPLICATION.PENDING' },
|
|
||||||
{ 'key': 'running', 'description': 'REPLICATION.RUNNING' },
|
|
||||||
{ 'key': 'error', 'description': 'REPLICATION.ERROR' },
|
|
||||||
{ 'key': 'retrying', 'description': 'REPLICATION.RETRYING' },
|
|
||||||
{ 'key': 'stopped' , 'description': 'REPLICATION.STOPPED' },
|
|
||||||
{ 'key': 'finished', 'description': 'REPLICATION.FINISHED' },
|
|
||||||
{ 'key': 'canceled', 'description': 'REPLICATION.CANCELED' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const optionalSearch: {} = {0: 'REPLICATION.ADVANCED', 1: 'REPLICATION.SIMPLE'};
|
|
||||||
|
|
||||||
class SearchOption {
|
|
||||||
policyId: number;
|
|
||||||
policyName: string = '';
|
|
||||||
repoName: string = '';
|
|
||||||
status: string = '';
|
|
||||||
startTime: string = '';
|
|
||||||
startTimestamp: string = '';
|
|
||||||
endTime: string = '';
|
|
||||||
endTimestamp: string = '';
|
|
||||||
page: number = 1;
|
|
||||||
pageSize: number = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'replicaton',
|
|
||||||
templateUrl: 'replication.component.html',
|
|
||||||
styleUrls: ['./replication.component.css']
|
|
||||||
})
|
|
||||||
export class ReplicationComponent implements OnInit {
|
|
||||||
|
|
||||||
projectId: number;
|
|
||||||
|
|
||||||
search: SearchOption = new SearchOption();
|
|
||||||
|
|
||||||
ruleStatus = ruleStatus;
|
|
||||||
currentRuleStatus: {key: string, description: string};
|
|
||||||
|
|
||||||
jobStatus = jobStatus;
|
|
||||||
currentJobStatus: {key: string, description: string};
|
|
||||||
|
|
||||||
changedPolicies: Policy[];
|
|
||||||
changedJobs: Job[];
|
|
||||||
initSelectedId: number;
|
|
||||||
|
|
||||||
policies: Policy[];
|
|
||||||
jobs: Job[];
|
|
||||||
|
|
||||||
jobsTotalRecordCount: number;
|
|
||||||
jobsTotalPage: number;
|
|
||||||
|
|
||||||
toggleJobSearchOption = optionalSearch;
|
|
||||||
currentJobSearchOption: number;
|
|
||||||
|
|
||||||
@ViewChild(CreateEditPolicyComponent)
|
|
||||||
createEditPolicyComponent: CreateEditPolicyComponent;
|
|
||||||
|
|
||||||
@ViewChild('fromTime') fromTimeInput: NgModel;
|
|
||||||
@ViewChild('toTime') toTimeInput: NgModel;
|
|
||||||
|
|
||||||
get fromTimeInvalid(): boolean {
|
|
||||||
return this.fromTimeInput.errors && this.fromTimeInput.errors.dateValidator && (this.fromTimeInput.dirty || this.fromTimeInput.touched);
|
|
||||||
}
|
|
||||||
|
|
||||||
get toTimeInvalid(): boolean {
|
|
||||||
return this.toTimeInput.errors && this.toTimeInput.errors.dateValidator && (this.toTimeInput.dirty || this.toTimeInput.touched);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private messageHandlerService: MessageHandlerService,
|
|
||||||
private replicationService: ReplicationService,
|
|
||||||
private route: ActivatedRoute) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
|
||||||
this.currentRuleStatus = this.ruleStatus[0];
|
|
||||||
this.currentJobStatus = this.jobStatus[0];
|
|
||||||
this.currentJobSearchOption = 0;
|
|
||||||
this.retrievePolicies();
|
|
||||||
|
|
||||||
let isCreate = this.route.snapshot.parent.queryParams['is_create'];
|
|
||||||
if (isCreate && <boolean>isCreate) {
|
|
||||||
this.openModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retrievePolicies(): void {
|
|
||||||
this.replicationService
|
|
||||||
.listPolicies(this.search.policyName, this.projectId)
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.changedPolicies = response || [];
|
|
||||||
if(this.changedPolicies && this.changedPolicies.length > 0) {
|
|
||||||
this.initSelectedId = this.changedPolicies[0].id;
|
|
||||||
}
|
|
||||||
this.policies = this.changedPolicies;
|
|
||||||
if(this.changedPolicies && this.changedPolicies.length > 0) {
|
|
||||||
this.search.policyId = this.changedPolicies[0].id;
|
|
||||||
this.fetchPolicyJobs();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error=>this.messageHandlerService.handleError(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
openModal(): void {
|
|
||||||
this.createEditPolicyComponent.openCreateEditPolicy(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
openEditPolicy(policy: Policy) {
|
|
||||||
if(policy) {
|
|
||||||
let editable = true;
|
|
||||||
if(policy.enabled === 1) {
|
|
||||||
editable = false;
|
|
||||||
}
|
|
||||||
this.createEditPolicyComponent.openCreateEditPolicy(editable, policy.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchPolicyJobs(state?: State) {
|
|
||||||
if(state) {
|
|
||||||
this.search.page = state.page.to + 1;
|
|
||||||
}
|
|
||||||
this.replicationService
|
|
||||||
.listJobs(this.search.policyId, this.search.status, this.search.repoName,
|
|
||||||
this.search.startTimestamp, this.search.endTimestamp, this.search.page, this.search.pageSize)
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.jobsTotalRecordCount = response.headers.get('x-total-count');
|
|
||||||
this.jobsTotalPage = Math.ceil(this.jobsTotalRecordCount / this.search.pageSize);
|
|
||||||
this.changedJobs = response.json();
|
|
||||||
this.jobs = this.changedJobs;
|
|
||||||
for(let i = 0; i < this.jobs.length; i++) {
|
|
||||||
let j = this.jobs[i];
|
|
||||||
if(j.status == 'retrying' || j.status == 'error') {
|
|
||||||
this.messageHandlerService.showError('REPLICATION.FOUND_ERROR_IN_JOBS', '');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error=>this.messageHandlerService.handleError(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectOnePolicy(policy: Policy) {
|
|
||||||
if(policy) {
|
|
||||||
this.search.policyId = policy.id;
|
|
||||||
this.search.repoName = '';
|
|
||||||
this.search.status = '';
|
|
||||||
this.currentJobSearchOption = 0;
|
|
||||||
this.currentJobStatus = { 'key': 'all', 'description': 'REPLICATION.ALL' };
|
|
||||||
this.fetchPolicyJobs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doSearchPolicies(policyName: string) {
|
|
||||||
this.search.policyName = policyName;
|
|
||||||
this.retrievePolicies();
|
|
||||||
}
|
|
||||||
|
|
||||||
doFilterPolicyStatus($event: any) {
|
|
||||||
if ($event && $event.target && $event.target["value"]) {
|
|
||||||
let status = $event.target["value"];
|
|
||||||
this.currentRuleStatus = this.ruleStatus.find(r=>r.key === status);
|
|
||||||
if(this.currentRuleStatus.key === 'all') {
|
|
||||||
this.changedPolicies = this.policies;
|
|
||||||
} else {
|
|
||||||
this.changedPolicies = this.policies.filter(policy=>policy.enabled === +this.currentRuleStatus.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doFilterJobStatus($event: any) {
|
|
||||||
if ($event && $event.target && $event.target["value"]) {
|
|
||||||
let status = $event.target["value"];
|
|
||||||
this.currentJobStatus = this.jobStatus.find(r=>r.key === status);
|
|
||||||
if(this.currentJobStatus.key === 'all') {
|
|
||||||
status = '';
|
|
||||||
}
|
|
||||||
this.search.status = status;
|
|
||||||
this.doSearchJobs(this.search.repoName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doSearchJobs(repoName: string) {
|
|
||||||
this.search.repoName = repoName;
|
|
||||||
this.fetchPolicyJobs();
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadPolicies(isReady: boolean) {
|
|
||||||
if(isReady) {
|
|
||||||
this.search.policyName = '';
|
|
||||||
this.retrievePolicies();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshPolicies() {
|
|
||||||
this.retrievePolicies();
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshJobs() {
|
|
||||||
this.fetchPolicyJobs();
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSearchJobOptionalName(option: number) {
|
|
||||||
(option === 1) ? this.currentJobSearchOption = 0 : this.currentJobSearchOption = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
convertDate(strDate: string): string {
|
|
||||||
if(/^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d$/.test(strDate)) {
|
|
||||||
let parts = strDate.split(/[-\/]/);
|
|
||||||
strDate = parts[2] /*Year*/ + '-' +parts[1] /*Month*/ + '-' + parts[0] /*Date*/;
|
|
||||||
}
|
|
||||||
return strDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
doJobSearchByStartTime(strDate: string) {
|
|
||||||
this.search.startTimestamp = '';
|
|
||||||
if(this.fromTimeInput.valid && strDate) {
|
|
||||||
strDate = this.convertDate(strDate);
|
|
||||||
this.search.startTimestamp = new Date(strDate).getTime() / 1000 + '';
|
|
||||||
}
|
|
||||||
this.fetchPolicyJobs();
|
|
||||||
}
|
|
||||||
|
|
||||||
doJobSearchByEndTime(strDate: string) {
|
|
||||||
this.search.endTimestamp = '';
|
|
||||||
if(this.toTimeInput.valid && strDate) {
|
|
||||||
strDate = this.convertDate(strDate);
|
|
||||||
let oneDayOffset = 3600 * 24;
|
|
||||||
this.search.endTimestamp = (new Date(strDate).getTime() / 1000 + oneDayOffset) + '';
|
|
||||||
}
|
|
||||||
this.fetchPolicyJobs();
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,16 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { ReplicationManagementComponent } from './replication-management/replication-management.component';
|
|
||||||
|
|
||||||
import { ReplicationComponent } from './replication.component';
|
import { ReplicationManagementComponent } from './replication-management/replication-management.component';
|
||||||
import { ListJobComponent } from './list-job/list-job.component';
|
import { ReplicationPageComponent } from './replication-page.component';
|
||||||
import { TotalReplicationComponent } from './total-replication/total-replication.component';
|
import { TotalReplicationPageComponent } from './total-replication/total-replication-page.component';
|
||||||
import { DestinationComponent } from './destination/destination.component';
|
import { DestinationPageComponent } from './destination/destination-page.component';
|
||||||
import { CreateEditDestinationComponent } from './create-edit-destination/create-edit-destination.component';
|
|
||||||
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { ReplicationService } from './replication.service';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -30,14 +27,15 @@ import { ReplicationService } from './replication.service';
|
|||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ReplicationComponent,
|
ReplicationPageComponent,
|
||||||
ReplicationManagementComponent,
|
ReplicationManagementComponent,
|
||||||
ListJobComponent,
|
TotalReplicationPageComponent,
|
||||||
TotalReplicationComponent,
|
DestinationPageComponent
|
||||||
DestinationComponent,
|
|
||||||
CreateEditDestinationComponent
|
|
||||||
],
|
],
|
||||||
exports: [ ReplicationComponent ],
|
exports: [
|
||||||
providers: [ ReplicationService ]
|
ReplicationPageComponent,
|
||||||
|
DestinationPageComponent,
|
||||||
|
TotalReplicationPageComponent
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class ReplicationModule {}
|
export class ReplicationModule { }
|
@ -1,179 +0,0 @@
|
|||||||
// 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 { Injectable } from '@angular/core';
|
|
||||||
import { Http, URLSearchParams, Response } from '@angular/http';
|
|
||||||
|
|
||||||
import { Policy } from './policy';
|
|
||||||
import { Job } from './job';
|
|
||||||
import { Target } from './target';
|
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import 'rxjs/add/operator/catch';
|
|
||||||
import 'rxjs/add/operator/map';
|
|
||||||
import 'rxjs/add/observable/throw';
|
|
||||||
import 'rxjs/add/operator/mergeMap';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ReplicationService {
|
|
||||||
constructor(private http: Http) {}
|
|
||||||
|
|
||||||
listPolicies(policyName: string, projectId?: any): Observable<Policy[]> {
|
|
||||||
if(!projectId) {
|
|
||||||
projectId = '';
|
|
||||||
}
|
|
||||||
return this.http
|
|
||||||
.get(`/api/policies/replication?project_id=${projectId}&name=${policyName}`)
|
|
||||||
.map(response=>response.json() as Policy[])
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
getPolicy(policyId: number): Observable<Policy> {
|
|
||||||
return this.http
|
|
||||||
.get(`/api/policies/replication/${policyId}`)
|
|
||||||
.map(response=>response.json() as Policy)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
createPolicy(policy: Policy): Observable<any> {
|
|
||||||
return this.http
|
|
||||||
.post(`/api/policies/replication`, JSON.stringify(policy))
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePolicy(policy: Policy): Observable<any> {
|
|
||||||
if (policy && policy.id) {
|
|
||||||
return this.http
|
|
||||||
.put(`/api/policies/replication/${policy.id}`, JSON.stringify(policy))
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
return Observable.throw(new Error("Policy is nil or has no ID set."));
|
|
||||||
}
|
|
||||||
|
|
||||||
createOrUpdatePolicyWithNewTarget(policy: Policy, target: Target): Observable<any> {
|
|
||||||
return this.http
|
|
||||||
.post(`/api/targets`, JSON.stringify(target))
|
|
||||||
.map(response=>{
|
|
||||||
return response.status;
|
|
||||||
})
|
|
||||||
.catch(error=>Observable.throw(error))
|
|
||||||
.flatMap((status)=>{
|
|
||||||
if(status === 201) {
|
|
||||||
return this.http
|
|
||||||
.get(`/api/targets?name=${target.name}`)
|
|
||||||
.map(res=>res)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatMap((res: Response) => {
|
|
||||||
if(res.status === 200) {
|
|
||||||
let lastAddedTarget= <Target>res.json()[0];
|
|
||||||
if(lastAddedTarget && lastAddedTarget.id) {
|
|
||||||
policy.target_id = lastAddedTarget.id;
|
|
||||||
if(policy.id) {
|
|
||||||
return this.http
|
|
||||||
.put(`/api/policies/replication/${policy.id}`, JSON.stringify(policy))
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
} else {
|
|
||||||
return this.http
|
|
||||||
.post(`/api/policies/replication`, JSON.stringify(policy))
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
enablePolicy(policyId: number, enabled: number): Observable<any> {
|
|
||||||
return this.http
|
|
||||||
.put(`/api/policies/replication/${policyId}/enablement`, {enabled: enabled})
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
deletePolicy(policyId: number): Observable<any> {
|
|
||||||
return this.http
|
|
||||||
.delete(`/api/policies/replication/${policyId}`)
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /api/jobs/replication/?page=1&page_size=20&end_time=&policy_id=1&start_time=&status=&repository=
|
|
||||||
listJobs(policyId: number, status: string = '', repoName: string = '', startTime: string = '', endTime: string = '', page: number, pageSize: number): Observable<any> {
|
|
||||||
return this.http
|
|
||||||
.get(`/api/jobs/replication?policy_id=${policyId}&status=${status}&repository=${repoName}&start_time=${startTime}&end_time=${endTime}&page=${page}&page_size=${pageSize}`)
|
|
||||||
.map(response=>response)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
listTargets(targetName: string): Observable<Target[]> {
|
|
||||||
return this.http
|
|
||||||
.get(`/api/targets?name=${targetName}`)
|
|
||||||
.map(response=>response.json() as Target[])
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
listTargetPolicies(targetId: number): Observable<Policy[]> {
|
|
||||||
return this.http
|
|
||||||
.get(`/api/targets/${targetId}/policies`)
|
|
||||||
.map(response=>response.json() as Policy[])
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
getTarget(targetId: number): Observable<Target> {
|
|
||||||
return this.http
|
|
||||||
.get(`/api/targets/${targetId}`)
|
|
||||||
.map(response=>response.json() as Target)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
createTarget(target: Target): Observable<any> {
|
|
||||||
return this.http
|
|
||||||
.post(`/api/targets`, JSON.stringify(target))
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
pingTarget(target: Target): Observable<any> {
|
|
||||||
if(target.id) {
|
|
||||||
return this.http
|
|
||||||
.post(`/api/targets/${target.id}/ping`, {})
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
return this.http
|
|
||||||
.post(`/api/targets/ping`, target)
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTarget(target: Target, targetId: number): Observable<any> {
|
|
||||||
return this.http
|
|
||||||
.put(`/api/targets/${targetId}`, JSON.stringify(target))
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTarget(targetId: number): Observable<any> {
|
|
||||||
return this.http
|
|
||||||
.delete(`/api/targets/${targetId}`)
|
|
||||||
.map(response=>response.status)
|
|
||||||
.catch(error=>Observable.throw(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
<div style="margin-top: 24px;">
|
||||||
|
<hbr-replication [withReplicationJob]='false'></hbr-replication>
|
||||||
|
</div>
|
@ -11,26 +11,11 @@
|
|||||||
// 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 } from '@angular/core';
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"status": "running",
|
|
||||||
"repository": "library/mysql",
|
|
||||||
"policy_id": 1,
|
|
||||||
"operation": "transfer",
|
|
||||||
"tags": null,
|
|
||||||
"creation_time": "2017-02-24T06:44:04Z",
|
|
||||||
"update_time": "2017-02-24T06:44:04Z"
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
@Component({
|
||||||
export class Job {
|
selector: 'total-replication',
|
||||||
id: number;
|
templateUrl: 'total-replication-page.component.html'
|
||||||
status: string;
|
})
|
||||||
repository: string;
|
export class TotalReplicationPageComponent {
|
||||||
policy_id: number;
|
|
||||||
operation: string;
|
|
||||||
tags: string;
|
|
||||||
creation_time: Date;
|
|
||||||
update_time: Date;
|
|
||||||
}
|
}
|
@ -1,15 +0,0 @@
|
|||||||
<div class="row">
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
|
||||||
<div class="row flex-items-xs-right option-right">
|
|
||||||
<div class="flex-items-xs-middle">
|
|
||||||
<grid-filter filterPlaceholder='{{"REPLICATION.FILTER_POLICIES_PLACEHOLDER" | translate}}' (filter)="doSearchPolicies($event)" [currentValue]="policyName"></grid-filter>
|
|
||||||
<a href="javascript:void(0)" (click)="refreshPolicies()"><clr-icon shape="refresh"></clr-icon></a>
|
|
||||||
</div>
|
|
||||||
<create-edit-policy [projectId]="projectId" (reload)="reloadPolicies($event)"></create-edit-policy>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
|
||||||
<list-policy [policies]="changedPolicies" [projectless]="true" (editOne)="openEditPolicy($event)" (selectOne)="selectPolicy($event)" (reload)="reloadPolicies($event)"></list-policy>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,95 +0,0 @@
|
|||||||
// 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, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
|
||||||
import { ReplicationService } from '../../replication/replication.service';
|
|
||||||
|
|
||||||
import { CreateEditPolicyComponent } from '../../shared/create-edit-policy/create-edit-policy.component';
|
|
||||||
|
|
||||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
|
||||||
|
|
||||||
import { Policy } from '../../replication/policy';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'total-replication',
|
|
||||||
templateUrl: 'total-replication.component.html',
|
|
||||||
providers: [ ReplicationService ],
|
|
||||||
styleUrls: ['./total-replication.component.css'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class TotalReplicationComponent implements OnInit {
|
|
||||||
|
|
||||||
changedPolicies: Policy[];
|
|
||||||
policies: Policy[];
|
|
||||||
policyName: string = '';
|
|
||||||
projectId: number;
|
|
||||||
|
|
||||||
@ViewChild(CreateEditPolicyComponent)
|
|
||||||
createEditPolicyComponent: CreateEditPolicyComponent;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private replicationService: ReplicationService,
|
|
||||||
private messageHandlerService: MessageHandlerService,
|
|
||||||
private ref: ChangeDetectorRef) {
|
|
||||||
let hnd = setInterval(()=>ref.markForCheck(), 100);
|
|
||||||
setTimeout(()=>clearInterval(hnd), 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.retrievePolicies();
|
|
||||||
}
|
|
||||||
|
|
||||||
retrievePolicies(): void {
|
|
||||||
this.replicationService
|
|
||||||
.listPolicies(this.policyName)
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.changedPolicies = response;
|
|
||||||
this.policies = this.changedPolicies;
|
|
||||||
},
|
|
||||||
error=>this.messageHandlerService.handleError(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
doSearchPolicies(policyName: string) {
|
|
||||||
this.policyName = policyName;
|
|
||||||
this.retrievePolicies();
|
|
||||||
}
|
|
||||||
|
|
||||||
openEditPolicy(policy: Policy) {
|
|
||||||
if(policy) {
|
|
||||||
let editable = true;
|
|
||||||
if(policy.enabled === 1) {
|
|
||||||
editable = false;
|
|
||||||
}
|
|
||||||
this.createEditPolicyComponent.openCreateEditPolicy(editable, policy.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPolicy(policy: Policy) {
|
|
||||||
if(policy) {
|
|
||||||
this.projectId = policy.project_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshPolicies() {
|
|
||||||
this.retrievePolicies();
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadPolicies(isReady: boolean) {
|
|
||||||
if(isReady) {
|
|
||||||
this.policyName = '';
|
|
||||||
this.retrievePolicies();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
.form-group-label-override {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
<clr-modal [(clrModalOpen)]="createEditPolicyOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
|
||||||
<h3 class="modal-title">{{modalTitle}}</h3>
|
|
||||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
|
||||||
<div class="modal-body" style="max-height: 85vh;">
|
|
||||||
<form #policyForm="ngForm">
|
|
||||||
<section class="form-block">
|
|
||||||
<div class="alert alert-warning" *ngIf="!editable">
|
|
||||||
<div class="alert-item">
|
|
||||||
<span class="alert-text">
|
|
||||||
{{'REPLICATION.CANNOT_EDIT' | translate}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="policy_name" class="col-md-4 form-group-label-override">{{'REPLICATION.NAME' | translate}}<span style="color: red">*</span></label>
|
|
||||||
<label for="policy_name" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="name.errors && (name.dirty || name.touched)" [class.valid]="name.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
|
|
||||||
<input type="text" id="policy_name" [(ngModel)]="createEditPolicy.name" name="name" size="20" #name="ngModel" required [readonly]="readonly">
|
|
||||||
<span class="tooltip-content" *ngIf="name.errors && name.errors.required && (name.dirty || name.touched)">
|
|
||||||
{{'REPLICATION.NAME_IS_REQUIRED' | translate}}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="policy_description" class="col-md-4 form-group-label-override">{{'REPLICATION.DESCRIPTION' | translate}}</label>
|
|
||||||
<textarea class="col-md-8" id="policy_description" row="3" [(ngModel)]="createEditPolicy.description" name="description" size="20" #description="ngModel" [readonly]="readonly"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-md-4 form-group-label-override">{{'REPLICATION.ENABLE' | translate}}</label>
|
|
||||||
<div class="checkbox-inline">
|
|
||||||
<input type="checkbox" id="policy_enable" [(ngModel)]="createEditPolicy.enable" name="enable" #enable="ngModel" [disabled]="untoggleable">
|
|
||||||
<label for="policy_enable"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="destination_name" class="col-md-4 form-group-label-override">{{'REPLICATION.DESTINATION_NAME' | translate}}<span style="color: red">*</span></label>
|
|
||||||
<div class="select" *ngIf="!isCreateDestination">
|
|
||||||
<select id="destination_name" [(ngModel)]="createEditPolicy.targetId" name="targetId" (change)="selectTarget()" [disabled]="testOngoing || readonly">
|
|
||||||
<option *ngFor="let t of targets" [value]="t.id" [selected]="t.id == createEditPolicy.targetId">{{t.name}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<label class="col-md-8" *ngIf="isCreateDestination" 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" [(ngModel)]="createEditPolicy.targetName" name="targetName" size="8" #targetName="ngModel" value="" required>
|
|
||||||
<span class="tooltip-content" *ngIf="targetName.errors && targetName.errors.required && (targetName.dirty || targetName.touched)">
|
|
||||||
{{'REPLICATION.DESTINATION_NAME_IS_REQUIRED' | translate}}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="checkbox-inline" *ngIf="showNewDestination">
|
|
||||||
<input type="checkbox" id="check_new" (click)="newDestination(checkedAddNew.checked)" #checkedAddNew [checked]="isCreateDestination" [disabled]="testOngoing || readonly">
|
|
||||||
<label for="check_new">{{'REPLICATION.NEW_DESTINATION' | translate}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="destination_url" class="col-md-4 form-group-label-override">{{'REPLICATION.DESTINATION_URL' | translate}}<span style="color: red">*</span></label>
|
|
||||||
<label for="destination_url" class="col-md-8" aria-haspopup="true" role="tooltip" [class.invalid]="endpointUrl.errors && (endpointUrl.dirty || endpointUrl.touched)" [class.valid]="endpointUrl.valid" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
|
|
||||||
<input type="text" id="destination_url" [disabled]="testOngoing" [readonly]="readonly || !isCreateDestination" [(ngModel)]="createEditPolicy.endpointUrl" size="20" name="endpointUrl" required #endpointUrl="ngModel">
|
|
||||||
<span class="tooltip-content" *ngIf="endpointUrl.errors && endpointUrl.errors.required && (endpointUrl.dirty || endpointUrl.touched)">
|
|
||||||
{{'REPLICATION.DESTINATION_URL_IS_REQUIRED' | translate}}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="destination_username" class="col-md-4 form-group-label-override">{{'REPLICATION.DESTINATION_USERNAME' | translate}}</label>
|
|
||||||
<input type="text" class="col-md-8" id="destination_username" [disabled]="testOngoing" [readonly]="readonly || !isCreateDestination" [(ngModel)]="createEditPolicy.username" size="20" name="username" #username="ngModel">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="destination_password" class="col-md-4 form-group-label-override">{{'REPLICATION.DESTINATION_PASSWORD' | translate}}</label>
|
|
||||||
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [readonly]="readonly || !isCreateDestination" [(ngModel)]="createEditPolicy.password" size="20" name="password" #password="ngModel">
|
|
||||||
</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">{{'REPLICATION.TEST_CONNECTION' | translate}}</button>
|
|
||||||
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate }}</button>
|
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="!policyForm.form.valid || testOngoing || !editable" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
|
||||||
</div>
|
|
||||||
</clr-modal>
|
|
@ -1,343 +0,0 @@
|
|||||||
// 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, OnInit, ViewChild, AfterViewChecked } from '@angular/core';
|
|
||||||
|
|
||||||
import { NgForm } from '@angular/forms';
|
|
||||||
|
|
||||||
import { CreateEditPolicy } from './create-edit-policy';
|
|
||||||
|
|
||||||
import { ReplicationService } from '../../replication/replication.service';
|
|
||||||
import { MessageHandlerService } from '../message-handler/message-handler.service';
|
|
||||||
import { ActionType } from '../../shared/shared.const';
|
|
||||||
|
|
||||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
|
||||||
|
|
||||||
import { Policy } from '../../replication/policy';
|
|
||||||
import { Target } from '../../replication/target';
|
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
const FAKE_PASSWORD: string = 'ywJZnDTM';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'create-edit-policy',
|
|
||||||
templateUrl: 'create-edit-policy.component.html',
|
|
||||||
styleUrls: [ 'create-edit-policy.component.css' ]
|
|
||||||
})
|
|
||||||
export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
|
||||||
|
|
||||||
modalTitle: string;
|
|
||||||
createEditPolicyOpened: boolean;
|
|
||||||
createEditPolicy: CreateEditPolicy = new CreateEditPolicy();
|
|
||||||
initVal: CreateEditPolicy = new CreateEditPolicy();
|
|
||||||
|
|
||||||
actionType: ActionType;
|
|
||||||
|
|
||||||
isCreateDestination: boolean;
|
|
||||||
@Input() projectId: number;
|
|
||||||
|
|
||||||
@Output() reload = new EventEmitter();
|
|
||||||
|
|
||||||
targets: Target[];
|
|
||||||
|
|
||||||
pingTestMessage: string;
|
|
||||||
testOngoing: boolean;
|
|
||||||
pingStatus: boolean;
|
|
||||||
|
|
||||||
policyForm: NgForm;
|
|
||||||
|
|
||||||
staticBackdrop: boolean = true;
|
|
||||||
closable: boolean = false;
|
|
||||||
|
|
||||||
@ViewChild('policyForm')
|
|
||||||
currentForm: NgForm;
|
|
||||||
|
|
||||||
hasChanged: boolean;
|
|
||||||
|
|
||||||
editable: boolean;
|
|
||||||
|
|
||||||
@ViewChild(InlineAlertComponent)
|
|
||||||
inlineAlert: InlineAlertComponent;
|
|
||||||
|
|
||||||
get readonly(): boolean {
|
|
||||||
return this.actionType === ActionType.EDIT && this.createEditPolicy.enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
get untoggleable(): boolean {
|
|
||||||
return this.actionType === ActionType.EDIT && this.initVal.enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
get showNewDestination(): boolean {
|
|
||||||
return this.actionType === ActionType.ADD_NEW || !this.createEditPolicy.enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private replicationService: ReplicationService,
|
|
||||||
private messageHandlerService: MessageHandlerService,
|
|
||||||
private translateService: TranslateService) {}
|
|
||||||
|
|
||||||
prepareTargets(targetId?: number) {
|
|
||||||
this.replicationService
|
|
||||||
.listTargets('')
|
|
||||||
.subscribe(
|
|
||||||
targets=>{
|
|
||||||
this.targets = targets;
|
|
||||||
if(this.targets && this.targets.length > 0) {
|
|
||||||
let initialTarget: Target;
|
|
||||||
(targetId) ? initialTarget = this.targets.find(t=>t.id==targetId) : initialTarget = this.targets[0];
|
|
||||||
this.createEditPolicy.targetId = initialTarget.id;
|
|
||||||
this.createEditPolicy.targetName = initialTarget.name;
|
|
||||||
this.createEditPolicy.endpointUrl = initialTarget.endpoint;
|
|
||||||
this.createEditPolicy.username = initialTarget.username;
|
|
||||||
this.createEditPolicy.password = FAKE_PASSWORD;
|
|
||||||
|
|
||||||
this.initVal.targetId = this.createEditPolicy.targetId;
|
|
||||||
this.initVal.endpointUrl = this.createEditPolicy.endpointUrl;
|
|
||||||
this.initVal.username = this.createEditPolicy.username;
|
|
||||||
this.initVal.password = this.createEditPolicy.password;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error=>{
|
|
||||||
this.messageHandlerService.handleError(error);
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {}
|
|
||||||
|
|
||||||
openCreateEditPolicy(editable: boolean, policyId?: number): void {
|
|
||||||
this.createEditPolicyOpened = true;
|
|
||||||
this.createEditPolicy = new CreateEditPolicy();
|
|
||||||
|
|
||||||
this.editable = editable;
|
|
||||||
|
|
||||||
this.isCreateDestination = false;
|
|
||||||
|
|
||||||
this.hasChanged = false;
|
|
||||||
|
|
||||||
this.pingTestMessage = '';
|
|
||||||
this.pingStatus = true;
|
|
||||||
this.testOngoing = false;
|
|
||||||
|
|
||||||
if(policyId) {
|
|
||||||
this.actionType = ActionType.EDIT;
|
|
||||||
this.translateService.get('REPLICATION.EDIT_POLICY_TITLE').subscribe(res=>this.modalTitle=res);
|
|
||||||
this.replicationService
|
|
||||||
.getPolicy(policyId)
|
|
||||||
.subscribe(
|
|
||||||
policy=>{
|
|
||||||
this.createEditPolicy.policyId = policyId;
|
|
||||||
this.createEditPolicy.name = policy.name;
|
|
||||||
this.createEditPolicy.description = policy.description;
|
|
||||||
this.createEditPolicy.enable = policy.enabled === 1? true : false;
|
|
||||||
this.prepareTargets(policy.target_id);
|
|
||||||
|
|
||||||
this.initVal.name = this.createEditPolicy.name;
|
|
||||||
this.initVal.description = this.createEditPolicy.description;
|
|
||||||
this.initVal.enable = this.createEditPolicy.enable;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.actionType = ActionType.ADD_NEW;
|
|
||||||
this.translateService.get('REPLICATION.ADD_POLICY').subscribe(res=>this.modalTitle=res);
|
|
||||||
this.prepareTargets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newDestination(checkedAddNew: boolean): void {
|
|
||||||
this.isCreateDestination = checkedAddNew;
|
|
||||||
if(this.isCreateDestination) {
|
|
||||||
this.createEditPolicy.targetName = '';
|
|
||||||
this.createEditPolicy.endpointUrl = '';
|
|
||||||
this.createEditPolicy.username = '';
|
|
||||||
this.createEditPolicy.password = '';
|
|
||||||
} else {
|
|
||||||
this.prepareTargets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectTarget(): void {
|
|
||||||
let result = this.targets.find(target=>target.id == this.createEditPolicy.targetId);
|
|
||||||
if(result) {
|
|
||||||
this.createEditPolicy.targetId = result.id;
|
|
||||||
this.createEditPolicy.endpointUrl = result.endpoint;
|
|
||||||
this.createEditPolicy.username = result.username;
|
|
||||||
this.createEditPolicy.password = FAKE_PASSWORD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPolicyByForm(): Policy {
|
|
||||||
let policy = new Policy();
|
|
||||||
policy.project_id = this.projectId;
|
|
||||||
policy.id = this.createEditPolicy.policyId;
|
|
||||||
policy.name = this.createEditPolicy.name;
|
|
||||||
policy.description = this.createEditPolicy.description;
|
|
||||||
policy.enabled = this.createEditPolicy.enable ? 1 : 0;
|
|
||||||
policy.target_id = this.createEditPolicy.targetId;
|
|
||||||
return policy;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTargetByForm(): Target {
|
|
||||||
let target = new Target();
|
|
||||||
target.id = this.createEditPolicy.targetId;
|
|
||||||
target.name = this.createEditPolicy.targetName;
|
|
||||||
target.endpoint = this.createEditPolicy.endpointUrl;
|
|
||||||
target.username = this.createEditPolicy.username;
|
|
||||||
target.password = this.createEditPolicy.password;
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
createPolicy(): void {
|
|
||||||
this.replicationService
|
|
||||||
.createPolicy(this.getPolicyByForm())
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.messageHandlerService.showSuccess('REPLICATION.CREATED_SUCCESS');
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
this.reload.emit(true);
|
|
||||||
},
|
|
||||||
error=>{
|
|
||||||
if(this.messageHandlerService.isAppLevel(error)) {
|
|
||||||
this.messageHandlerService.handleError(error);
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
} else if (error.status === 409) {
|
|
||||||
this.inlineAlert.showInlineError('REPLICATION.POLICY_ALREADY_EXISTS');
|
|
||||||
} else {
|
|
||||||
this.inlineAlert.showInlineError(error);
|
|
||||||
}
|
|
||||||
console.error('Failed to create policy:' + error.status + ', error message:' + JSON.stringify(error['_body']));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createOrUpdatePolicyAndCreateTarget(): void {
|
|
||||||
this.replicationService
|
|
||||||
.createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm())
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.messageHandlerService.showSuccess('REPLICATION.CREATED_SUCCESS');
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
this.reload.emit(true);
|
|
||||||
},
|
|
||||||
error=>{
|
|
||||||
if(this.messageHandlerService.isAppLevel(error)) {
|
|
||||||
this.messageHandlerService.handleError(error);
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
} else if (error.status === 409) {
|
|
||||||
this.inlineAlert.showInlineError('REPLICATION.POLICY_ALREADY_EXISTS');
|
|
||||||
} else {
|
|
||||||
this.inlineAlert.showInlineError(error);
|
|
||||||
}
|
|
||||||
console.error('Failed to create policy and target:' + error.status + ', error message:' + JSON.stringify(error['_body']));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePolicy(): void {
|
|
||||||
this.replicationService
|
|
||||||
.updatePolicy(this.getPolicyByForm())
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.messageHandlerService.showSuccess('REPLICATION.UPDATED_SUCCESS')
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
this.reload.emit(true);
|
|
||||||
},
|
|
||||||
error=>{
|
|
||||||
if(this.messageHandlerService.isAppLevel(error)) {
|
|
||||||
this.messageHandlerService.handleError(error);
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
} else if (error.status === 409) {
|
|
||||||
this.inlineAlert.showInlineError('REPLICATION.POLICY_ALREADY_EXISTS');
|
|
||||||
} else {
|
|
||||||
this.inlineAlert.showInlineError(error);
|
|
||||||
}
|
|
||||||
console.error('Failed to create policy and target:' + error.status + ', error message:' + JSON.stringify(error['_body']));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
if(this.isCreateDestination) {
|
|
||||||
this.createOrUpdatePolicyAndCreateTarget();
|
|
||||||
} else {
|
|
||||||
if(this.actionType === ActionType.ADD_NEW) {
|
|
||||||
this.createPolicy();
|
|
||||||
} else if(this.actionType === ActionType.EDIT){
|
|
||||||
this.updatePolicy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onCancel() {
|
|
||||||
if(this.hasChanged) {
|
|
||||||
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
|
||||||
} else {
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
this.policyForm.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmCancel(confirmed: boolean) {
|
|
||||||
this.createEditPolicyOpened = false;
|
|
||||||
this.inlineAlert.close();
|
|
||||||
this.policyForm.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewChecked(): void {
|
|
||||||
this.policyForm = this.currentForm;
|
|
||||||
if(this.policyForm) {
|
|
||||||
this.policyForm.valueChanges.subscribe(data=>{
|
|
||||||
for(let i in data) {
|
|
||||||
let origin = this.initVal[i];
|
|
||||||
let current = data[i];
|
|
||||||
if(((this.actionType === ActionType.EDIT && !this.readonly && !current ) || current) && current !== origin) {
|
|
||||||
this.hasChanged = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
this.hasChanged = false;
|
|
||||||
this.inlineAlert.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testConnection() {
|
|
||||||
this.pingStatus = true;
|
|
||||||
this.translateService.get('REPLICATION.TESTING_CONNECTION').subscribe(res=>this.pingTestMessage=res);
|
|
||||||
this.testOngoing = !this.testOngoing;
|
|
||||||
let pingTarget: Target | any = {};
|
|
||||||
if(this.isCreateDestination) {
|
|
||||||
pingTarget.endpoint = this.createEditPolicy.endpointUrl;
|
|
||||||
pingTarget.username = this.createEditPolicy.username;
|
|
||||||
pingTarget.password = this.createEditPolicy.password;
|
|
||||||
} else {
|
|
||||||
pingTarget.id = this.createEditPolicy.targetId;
|
|
||||||
}
|
|
||||||
this.replicationService
|
|
||||||
.pingTarget(pingTarget)
|
|
||||||
.subscribe(
|
|
||||||
response=>{
|
|
||||||
this.testOngoing = !this.testOngoing;
|
|
||||||
this.translateService.get('REPLICATION.TEST_CONNECTION_SUCCESS').subscribe(res=>this.pingTestMessage=res);
|
|
||||||
this.pingStatus = true;
|
|
||||||
},
|
|
||||||
error=>{
|
|
||||||
this.testOngoing = !this.testOngoing;
|
|
||||||
this.translateService.get('REPLICATION.TEST_CONNECTION_FAILURE').subscribe(res=>this.pingTestMessage=res);
|
|
||||||
this.pingStatus = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
<clr-datagrid>
|
|
||||||
<clr-dg-column>{{'REPLICATION.NAME' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column *ngIf="projectless">{{'REPLICATION.PROJECT' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.DESTINATION_NAME' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.LAST_START_TIME' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-column>{{'REPLICATION.ACTIVATION' | translate}}</clr-dg-column>
|
|
||||||
<clr-dg-row *clrDgItems="let p of policies" [clrDgItem]="p" (click)="selectPolicy(p)" [style.backgroundColor]="(!projectless && selectedId === p.id) ? '#eee' : ''">
|
|
||||||
<clr-dg-action-overflow>
|
|
||||||
<button class="action-item" (click)="editPolicy(p)">{{'REPLICATION.EDIT_POLICY' | translate}}</button>
|
|
||||||
<button class="action-item" (click)="togglePolicy(p)">{{ (p.enabled === 0 ? 'REPLICATION.ENABLE' : 'REPLICATION.DISABLE') | translate}}</button>
|
|
||||||
<button class="action-item" (click)="deletePolicy(p)">{{'REPLICATION.DELETE_POLICY' | translate}}</button>
|
|
||||||
</clr-dg-action-overflow>
|
|
||||||
<clr-dg-cell>
|
|
||||||
<ng-template [ngIf]="projectless">
|
|
||||||
<a href="javascript:void(0)" [routerLink]="['/harbor', 'projects', p.project_id, 'replication']">{{p.name}}</a>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="!projectless">
|
|
||||||
{{p.name}}
|
|
||||||
</ng-template>
|
|
||||||
</clr-dg-cell>
|
|
||||||
<clr-dg-cell *ngIf="projectless">{{p.project_name}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{p.description ? p.description : '-'}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>{{p.target_name}}</clr-dg-cell>
|
|
||||||
<clr-dg-cell>
|
|
||||||
<ng-template [ngIf]="p.start_time === nullTime">-</ng-template>
|
|
||||||
<ng-template [ngIf]="p.start_time !== nullTime">{{p.start_time | date: 'short'}}</ng-template>
|
|
||||||
</clr-dg-cell>
|
|
||||||
<clr-dg-cell>
|
|
||||||
{{ (p.enabled === 1 ? 'REPLICATION.ENABLED' : 'REPLICATION.DISABLED') | translate}}
|
|
||||||
</clr-dg-cell>
|
|
||||||
</clr-dg-row>
|
|
||||||
<clr-dg-footer>{{ (policies ? policies.length : 0) }} {{'REPLICATION.ITEMS' | translate}}</clr-dg-footer>
|
|
||||||
</clr-datagrid>
|
|
@ -1,143 +0,0 @@
|
|||||||
// 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, ViewChild, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
|
||||||
|
|
||||||
import { ReplicationService } from '../../replication/replication.service';
|
|
||||||
import { Policy } from '../../replication/policy';
|
|
||||||
|
|
||||||
import { ConfirmationDialogService } from '../../shared/confirmation-dialog/confirmation-dialog.service';
|
|
||||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
|
||||||
|
|
||||||
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../../shared/shared.const';
|
|
||||||
|
|
||||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
|
||||||
|
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'list-policy',
|
|
||||||
templateUrl: 'list-policy.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ListPolicyComponent implements OnDestroy {
|
|
||||||
|
|
||||||
nullTime: string = '0001-01-01T00:00:00Z';
|
|
||||||
|
|
||||||
@Input() policies: Policy[];
|
|
||||||
@Input() projectless: boolean;
|
|
||||||
@Input() selectedId: number;
|
|
||||||
|
|
||||||
@Output() reload = new EventEmitter<boolean>();
|
|
||||||
@Output() selectOne = new EventEmitter<Policy>();
|
|
||||||
@Output() editOne = new EventEmitter<Policy>();
|
|
||||||
@Output() toggleOne = new EventEmitter<Policy>();
|
|
||||||
|
|
||||||
toggleSubscription: Subscription;
|
|
||||||
subscription: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private replicationService: ReplicationService,
|
|
||||||
private toggleConfirmDialogService: ConfirmationDialogService,
|
|
||||||
private deletionDialogService: ConfirmationDialogService,
|
|
||||||
private messageHandlerService: MessageHandlerService,
|
|
||||||
private ref: ChangeDetectorRef) {
|
|
||||||
setInterval(()=>ref.markForCheck(), 500);
|
|
||||||
this.toggleSubscription = this.toggleConfirmDialogService
|
|
||||||
.confirmationConfirm$
|
|
||||||
.subscribe(
|
|
||||||
message=> {
|
|
||||||
if(message &&
|
|
||||||
message.source === ConfirmationTargets.TOGGLE_CONFIRM &&
|
|
||||||
message.state === ConfirmationState.CONFIRMED) {
|
|
||||||
let policy: Policy = message.data;
|
|
||||||
policy.enabled = policy.enabled === 0 ? 1 : 0;
|
|
||||||
this.replicationService
|
|
||||||
.enablePolicy(policy.id, policy.enabled)
|
|
||||||
.subscribe(
|
|
||||||
response => {
|
|
||||||
this.messageHandlerService.showSuccess('REPLICATION.TOGGLED_SUCCESS');
|
|
||||||
},
|
|
||||||
error => this.messageHandlerService.handleError(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.subscription = this.deletionDialogService
|
|
||||||
.confirmationConfirm$
|
|
||||||
.subscribe(
|
|
||||||
message => {
|
|
||||||
if (message &&
|
|
||||||
message.source === ConfirmationTargets.POLICY &&
|
|
||||||
message.state === ConfirmationState.CONFIRMED) {
|
|
||||||
this.replicationService
|
|
||||||
.deletePolicy(message.data)
|
|
||||||
.subscribe(
|
|
||||||
response => {
|
|
||||||
this.messageHandlerService.showSuccess('REPLICATION.DELETED_SUCCESS');
|
|
||||||
this.reload.emit(true);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
if(error && error.status === 412) {
|
|
||||||
this.messageHandlerService.handleError('REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED');
|
|
||||||
} else {
|
|
||||||
this.messageHandlerService.handleError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
if (this.subscription) {
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if(this.toggleSubscription) {
|
|
||||||
this.toggleSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPolicy(policy: Policy): void {
|
|
||||||
this.selectedId = policy.id;
|
|
||||||
this.selectOne.emit(policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
editPolicy(policy: Policy) {
|
|
||||||
this.editOne.emit(policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
togglePolicy(policy: Policy) {
|
|
||||||
let toggleConfirmMessage: ConfirmationMessage = new ConfirmationMessage(
|
|
||||||
policy.enabled === 1 ? 'REPLICATION.TOGGLE_DISABLE_TITLE' : 'REPLICATION.TOGGLE_ENABLE_TITLE',
|
|
||||||
policy.enabled === 1 ? 'REPLICATION.CONFIRM_TOGGLE_DISABLE_POLICY': 'REPLICATION.CONFIRM_TOGGLE_ENABLE_POLICY',
|
|
||||||
policy.name,
|
|
||||||
policy,
|
|
||||||
ConfirmationTargets.TOGGLE_CONFIRM
|
|
||||||
);
|
|
||||||
this.toggleConfirmDialogService.openComfirmDialog(toggleConfirmMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
deletePolicy(policy: Policy) {
|
|
||||||
let deletionMessage: ConfirmationMessage = new ConfirmationMessage(
|
|
||||||
'REPLICATION.DELETION_TITLE',
|
|
||||||
'REPLICATION.DELETION_SUMMARY',
|
|
||||||
policy.name,
|
|
||||||
policy.id,
|
|
||||||
ConfirmationTargets.POLICY,
|
|
||||||
ConfirmationButtons.DELETE_CANCEL);
|
|
||||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -20,8 +20,10 @@ import { errorHandler } from '../../shared/shared.utils';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { SessionService } from '../../shared/session.service';
|
import { SessionService } from '../../shared/session.service';
|
||||||
|
|
||||||
|
import { ErrorHandler } from 'harbor-ui';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MessageHandlerService {
|
export class MessageHandlerService implements ErrorHandler{
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private msgService: MessageService,
|
private msgService: MessageService,
|
||||||
@ -85,4 +87,20 @@ export class MessageHandlerService {
|
|||||||
public isAppLevel(error: any): boolean {
|
public isAppLevel(error: any): boolean {
|
||||||
return error && error.statusCode === httpStatusCode.Unauthorized;
|
return error && error.statusCode === httpStatusCode.Unauthorized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public error(error: any): void {
|
||||||
|
this.handleError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warning(warning: any): void {
|
||||||
|
this.showWarning(warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(info: any): void {
|
||||||
|
this.showSuccess(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public log(log: any): void {
|
||||||
|
this.showInfo(log);
|
||||||
|
}
|
||||||
}
|
}
|
@ -31,9 +31,6 @@ import { SystemAdminGuard } from './route/system-admin-activate.service';
|
|||||||
import { NewUserFormComponent } from './new-user-form/new-user-form.component';
|
import { NewUserFormComponent } from './new-user-form/new-user-form.component';
|
||||||
import { InlineAlertComponent } from './inline-alert/inline-alert.component';
|
import { InlineAlertComponent } from './inline-alert/inline-alert.component';
|
||||||
|
|
||||||
import { ListPolicyComponent } from './list-policy/list-policy.component';
|
|
||||||
import { CreateEditPolicyComponent } from './create-edit-policy/create-edit-policy.component';
|
|
||||||
|
|
||||||
import { PortValidatorDirective } from './port.directive';
|
import { PortValidatorDirective } from './port.directive';
|
||||||
|
|
||||||
import { PageNotFoundComponent } from './not-found/not-found.component';
|
import { PageNotFoundComponent } from './not-found/not-found.component';
|
||||||
@ -56,11 +53,30 @@ import { GaugeComponent } from './gauge/gauge.component';
|
|||||||
import { StatisticHandler } from './statictics/statistic-handler.service';
|
import { StatisticHandler } from './statictics/statistic-handler.service';
|
||||||
import { DateValidatorDirective } from '../shared/date-validator.directive';
|
import { DateValidatorDirective } from '../shared/date-validator.directive';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IServiceConfig,
|
||||||
|
SERVICE_CONFIG,
|
||||||
|
ErrorHandler,
|
||||||
|
HarborLibraryModule
|
||||||
|
} from 'harbor-ui';
|
||||||
|
|
||||||
|
const uiLibConfig: IServiceConfig = {
|
||||||
|
enablei18Support: true,
|
||||||
|
langCookieKey: "harbor-lang",
|
||||||
|
langMessageLoader: "http",
|
||||||
|
langMessagePathForHttpLoader: "src/i18n/lang/",
|
||||||
|
langMessageFileSuffixForHttpLoader: "-lang.json",
|
||||||
|
};
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CoreModule,
|
CoreModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
RouterModule
|
RouterModule,
|
||||||
|
HarborLibraryModule.forRoot({
|
||||||
|
config: { provide: SERVICE_CONFIG, useValue: uiLibConfig },
|
||||||
|
errorHandler: { provide: ErrorHandler, useClass: MessageHandlerService }
|
||||||
|
})
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
MessageComponent,
|
MessageComponent,
|
||||||
@ -69,8 +85,6 @@ import { DateValidatorDirective } from '../shared/date-validator.directive';
|
|||||||
ConfirmationDialogComponent,
|
ConfirmationDialogComponent,
|
||||||
NewUserFormComponent,
|
NewUserFormComponent,
|
||||||
InlineAlertComponent,
|
InlineAlertComponent,
|
||||||
ListPolicyComponent,
|
|
||||||
CreateEditPolicyComponent,
|
|
||||||
PortValidatorDirective,
|
PortValidatorDirective,
|
||||||
PageNotFoundComponent,
|
PageNotFoundComponent,
|
||||||
AboutDialogComponent,
|
AboutDialogComponent,
|
||||||
@ -84,6 +98,7 @@ import { DateValidatorDirective } from '../shared/date-validator.directive';
|
|||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CoreModule,
|
CoreModule,
|
||||||
|
HarborLibraryModule,
|
||||||
MessageComponent,
|
MessageComponent,
|
||||||
MaxLengthExtValidatorDirective,
|
MaxLengthExtValidatorDirective,
|
||||||
FilterComponent,
|
FilterComponent,
|
||||||
@ -91,8 +106,6 @@ import { DateValidatorDirective } from '../shared/date-validator.directive';
|
|||||||
ConfirmationDialogComponent,
|
ConfirmationDialogComponent,
|
||||||
NewUserFormComponent,
|
NewUserFormComponent,
|
||||||
InlineAlertComponent,
|
InlineAlertComponent,
|
||||||
ListPolicyComponent,
|
|
||||||
CreateEditPolicyComponent,
|
|
||||||
PortValidatorDirective,
|
PortValidatorDirective,
|
||||||
PageNotFoundComponent,
|
PageNotFoundComponent,
|
||||||
AboutDialogComponent,
|
AboutDialogComponent,
|
||||||
|
@ -256,6 +256,7 @@
|
|||||||
"CREATION_TIME": "Start Time",
|
"CREATION_TIME": "Start Time",
|
||||||
"END_TIME": "End Time",
|
"END_TIME": "End Time",
|
||||||
"LOGS": "Logs",
|
"LOGS": "Logs",
|
||||||
|
"OF": "of",
|
||||||
"ITEMS": "item(s)",
|
"ITEMS": "item(s)",
|
||||||
"TOGGLE_ENABLE_TITLE": "Enable Rule",
|
"TOGGLE_ENABLE_TITLE": "Enable Rule",
|
||||||
"CONFIRM_TOGGLE_ENABLE_POLICY": "After enabling the replication rule, all repositories under the project will be replicated to the destination registry. \nPlease confirm to continue.",
|
"CONFIRM_TOGGLE_ENABLE_POLICY": "After enabling the replication rule, all repositories under the project will be replicated to the destination registry. \nPlease confirm to continue.",
|
||||||
@ -294,6 +295,7 @@
|
|||||||
"INVALID_NAME": "Invalid endpoint name.",
|
"INVALID_NAME": "Invalid endpoint name.",
|
||||||
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
||||||
"CREATION_TIME": "Creation Time",
|
"CREATION_TIME": "Creation Time",
|
||||||
|
"OF": "of",
|
||||||
"ITEMS": "item(s)",
|
"ITEMS": "item(s)",
|
||||||
"CREATED_SUCCESS": "Created endpoint successfully.",
|
"CREATED_SUCCESS": "Created endpoint successfully.",
|
||||||
"UPDATED_SUCCESS": "Updated endpoint successfully.",
|
"UPDATED_SUCCESS": "Updated endpoint successfully.",
|
||||||
|
@ -256,6 +256,7 @@
|
|||||||
"CREATION_TIME": "Fecha de Inicio",
|
"CREATION_TIME": "Fecha de Inicio",
|
||||||
"END_TIME": "Fecha de Finalización",
|
"END_TIME": "Fecha de Finalización",
|
||||||
"LOGS": "Logs",
|
"LOGS": "Logs",
|
||||||
|
"OF": "of",
|
||||||
"ITEMS": "elemento(s)",
|
"ITEMS": "elemento(s)",
|
||||||
"TOGGLE_ENABLE_TITLE": "Activar Regla",
|
"TOGGLE_ENABLE_TITLE": "Activar Regla",
|
||||||
"CONFIRM_TOGGLE_ENABLE_POLICY": "Después de la activación de esta regla, todos los repositorios de este proyecto serán replicados al registro de destino.\nPor favor, confirme para continuar.",
|
"CONFIRM_TOGGLE_ENABLE_POLICY": "Después de la activación de esta regla, todos los repositorios de este proyecto serán replicados al registro de destino.\nPor favor, confirme para continuar.",
|
||||||
@ -294,6 +295,7 @@
|
|||||||
"INVALID_NAME": "El nombre del endpoint no es válido.",
|
"INVALID_NAME": "El nombre del endpoint no es válido.",
|
||||||
"FAILED_TO_GET_TARGET": "Fallo al obtener el endpoint.",
|
"FAILED_TO_GET_TARGET": "Fallo al obtener el endpoint.",
|
||||||
"CREATION_TIME": "Fecha de creación",
|
"CREATION_TIME": "Fecha de creación",
|
||||||
|
"OF": "of",
|
||||||
"ITEMS": "elemento(s)",
|
"ITEMS": "elemento(s)",
|
||||||
"CREATED_SUCCESS": "Endpoint creado satisfactoriamente.",
|
"CREATED_SUCCESS": "Endpoint creado satisfactoriamente.",
|
||||||
"UPDATED_SUCCESS": "Endpoint actualizado satisfactoriamente.",
|
"UPDATED_SUCCESS": "Endpoint actualizado satisfactoriamente.",
|
||||||
|
@ -256,6 +256,7 @@
|
|||||||
"CREATION_TIME": "创建时间",
|
"CREATION_TIME": "创建时间",
|
||||||
"END_TIME": "结束时间",
|
"END_TIME": "结束时间",
|
||||||
"LOGS": "日志",
|
"LOGS": "日志",
|
||||||
|
"OF": "共计",
|
||||||
"ITEMS": "条记录",
|
"ITEMS": "条记录",
|
||||||
"TOGGLE_ENABLE_TITLE": "启用规则",
|
"TOGGLE_ENABLE_TITLE": "启用规则",
|
||||||
"CONFIRM_TOGGLE_ENABLE_POLICY": "启用规则后,该项目下的所有镜像仓库将复制到目标实例。\n请确认继续。",
|
"CONFIRM_TOGGLE_ENABLE_POLICY": "启用规则后,该项目下的所有镜像仓库将复制到目标实例。\n请确认继续。",
|
||||||
@ -294,6 +295,7 @@
|
|||||||
"INVALID_NAME": "无效的目标名称。",
|
"INVALID_NAME": "无效的目标名称。",
|
||||||
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
||||||
"CREATION_TIME": "创建时间",
|
"CREATION_TIME": "创建时间",
|
||||||
|
"OF": "共计",
|
||||||
"ITEMS": "条记录",
|
"ITEMS": "条记录",
|
||||||
"CREATED_SUCCESS": "成功创建目标。",
|
"CREATED_SUCCESS": "成功创建目标。",
|
||||||
"UPDATED_SUCCESS": "成功更新目标。",
|
"UPDATED_SUCCESS": "成功更新目标。",
|
||||||
|
Loading…
Reference in New Issue
Block a user