Merge pull request #4354 from pengpengshui/reptolib

Modify replication rule from page to dialog #4296
This commit is contained in:
pengpengshui 2018-03-07 17:34:13 +08:00 committed by GitHub
commit 7bf277df6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1308 additions and 2025 deletions

View File

@ -74,13 +74,14 @@ Use **withTitle** to set whether self-contained a header with title or not. Defa
Support two different display scope mode: under specific project or whole system.
If **projectId** is set to the id of specified project, then only show the replication rules bound with the project. Otherwise, show all the rules of the whole system.
On specific project mode, without need projectId, but also need to provide projectName for display.
**withReplicationJob** is used to determine whether or not show the replication jobs which are relevant with the selected replication rule.
**readonly** is to disable all the create/edit/delete actions.
**isSystemAdmin** is for judgment if user has administrator privilege, if true, user can do the create/edit/delete/replicate actions.
```
<hbr-replication [projectId]="..." [withReplicationJob]='...' [readonly]="..."></hbr-replication>
<hbr-replication [projectId]="..." [projectName]="..." [withReplicationJob]='...' [isSystemAdmin]="..."></hbr-replication>
```
* **Endpoint Management View**

View File

@ -1,5 +1,70 @@
export const CREATE_EDIT_RULE_STYLE: string = `
.form-group-label-override {
font-size: 14px;
font-weight: 400;
}`;
/**
* Created by pengf on 9/28/2017.
*/
.select{
width: 186px;
}
.select .optionMore{
background-color: #bfbaba;
height: 1.6em;
font-size: 1.2em;
cursor: pointer;
text-align: center;
}
.hideFilter{ display: none;}
h4{
color: #666;
}
.colorRed{color: red;}
.colorRed a{text-decoration: underline;color: #007CBB;}
.alertLabel{display:block; margin-top:0; line-height:1em; font-size:12px;}
.inputWidth{width: 270px;}
.endpointSelect{ width: 270px; margin-right: 20px;}
.filterSelect{width: 315px;}
.filterSelect clr-icon{margin-left: 15px;}
.filterSelect label{width: 136px;}
.filterSelect label input{width: 100%;}
.cursor{cursor: pointer;}
.pull-left{float: left;}
.padLeft0{padding-left: 0;}
.floatSetPar{display: inline-block; width: 120px;margin-right: 10px;}
.floatSet {display: inline-block; width: 82px;margin-right: 4px;}
.form-group{ min-height: 36px;}
.projectInput{float: left;position: relative;}
.switchIcon{width:20px;height:20px; margin-top: 10px;margin-left: 10px; cursor: pointer;}
.addEndpoint{ margin-top: .25em !important;padding-left:2px;padding-right:2px;min-width:58px;margin-right:0}
.shadow{position: absolute;top: 8px;}
.is-solid{cursor: pointer;}
.selectBox{
position: absolute;
width: 100%;
height: auto;
margin-top:-0.25rem;
border: 1px solid #ccc;
background-color: white;
border: 1px solid rgba(0,0,0,.15);
border-right-width: 2px;
border-bottom-width: 2px;
border-radius: 6px;
box-shadow: 0 5px 10px rgba(0,0,0,.2);
z-index: 100;
}
.selectBox ul li{
list-style: none;
padding: 3px 20px
}
.selectBox ul li:hover{
color: #262626;
background-image: linear-gradient(180deg,#f5f5f5 0,#e8e8e8);
background-repeat: repeat-x;
}
.form-group-override{
padding-left: 170px !important;
}
.form-group>label:first-child{font-size:14px; width:6.5rem;}
.goLink{color:blue; border-bottom:1px solid blue; line-height:14px; cursor:pointer;}
`;

View File

@ -1,100 +1,134 @@
export const CREATE_EDIT_RULE_TEMPLATE: string = `
<clr-modal [(clrModalOpen)]="createEditRuleOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
<h3 class="modal-title">{{modalTitle}}</h3>
<clr-modal [(clrModalOpen)]="createEditRuleOpened" [clrModalStaticBackdrop]="true" [clrModalClosable]="false">
<h3 class="modal-title">{{headerTitle | translate}}</h3>
<hbr-inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></hbr-inline-alert>
<div class="modal-body" style="max-height: 85vh;">
<form #ruleForm="ngForm">
<section class="form-block">
<div class="alert alert-warning" *ngIf="!editable">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
</div>
<span class="alert-text">
{{'REPLICATION.CANNOT_EDIT' | translate}}
</span>
</div>
<form [formGroup]="ruleForm" novalidate>
<section class="form-block">
<div class="form-group form-group-override">
<label class="form-group-label-override required">{{'REPLICATION.NAME' | translate}}</label>
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
[class.invalid]='(ruleForm.controls.name.touched && ruleForm.controls.name.invalid) || isRuleNameExist'>
<input type="text" id="ruleName" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" class="inputWidth" required maxlength="255" formControlName="name" #ruleName (keyup)='checkRuleName()' autocomplete="off">
<span class="tooltip-content">{{ruleNameTooltip | translate}}</span>
</label><span class="spinner spinner-inline spinner-pos" [hidden]="!inNameChecking"></span>
</div>
<!--Description-->
<div class="form-group form-group-override">
<label class="form-group-label-override">{{'REPLICATION.DESCRIPTION' | translate}}</label>
<textarea type="text" id="ruleDescription" class="inputWidth" row= 3; formControlName="description"></textarea>
</div>
<!--Projects-->
<div class="form-group form-group-override">
<label class="form-group-label-override required">{{'REPLICATION.SOURCE' | translate}}&nbsp;{{'PROJECT.PROJECTS' | translate | lowercase}}</label>
<div formArrayName="projects">
<div class="projectInput inputWidth" *ngFor="let project of projects.controls; let i= index" [formGroupName]="i" (mouseleave)="leaveInput()">
<input *ngIf="!projectId" formControlName="name" type="text" class="inputWidth" value="name" required
pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" (keyup)='handleValidation()' (focus)="focusClear($event)" autocomplete="off">
<input *ngIf="projectId" formControlName="name" type="text" class="inputWidth" value="name" readonly>
<div class="selectBox inputWidth" [style.display]="selectedProjectList.length ? 'block' : 'none'" >
<ul>
<li *ngFor="let project of selectedProjectList" (click)="selectedProjectName(project?.name)">{{project?.name}}</li>
</ul>
</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="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
<input type="text" id="policy_name" [(ngModel)]="createEditRule.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>
</div>
<label *ngIf="noProjectInfo.length != 0" class="colorRed alertLabel">{{noProjectInfo | translate}}</label>
</div>
<!--images/Filter-->
<div class="form-group form-group-override">
<label class="form-group-label-override">{{'REPLICATION.SOURCE_IMAGES_FILTER' | translate}}</label>
<div formArrayName="filters">
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index" [formGroupName]="i">
<div>
<div class="select floatSetPar">
<select formControlName="kind" (change)="filterChange($event)" id="{{i}}" name="{{filterListData[i]?.name}}">
<option *ngFor="let filter of filterListData[i]?.options;" value="{{filter}}">{{filter}}</option>
</select>
</div>
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
[class.invalid]='ruleForm.controls.filters.controls[i].controls.pattern.touched && ruleForm.controls.filters.controls[i].controls.pattern.invalid'>
<input type="text" #filterValue required size="14" formControlName="pattern">
<span class="tooltip-content">{{'TOOLTIP.EMPTY' | translate}}</span>
</label>
<clr-icon shape="times-circle" class="is-solid" (click)="deleteFilter(i)"></clr-icon>
</div>
</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)]="createEditRule.description" name="description" size="20" #description="ngModel" [readonly]="readonly"></textarea>
</div>
<clr-icon shape="plus-circle" class="is-solid" [hidden]="isFilterHide" (click)="addNewFilter()" style="margin-top: 11px;"></clr-icon>
</div>
<!--Targets-->
<div class="form-group form-group-override">
<label class="form-group-label-override required">{{'DESTINATION.ENDPOINT' | translate}}</label>
<div formArrayName="targets">
<div class="select endpointSelect pull-left" *ngFor="let target of targets.controls; let i= index" [formGroupName]="i">
<select id="ruleTarget" (change)="targetChange($event)" formControlName="id">
<option *ngFor="let target of targetList" value="{{target.id}}">{{target.name}}-{{target.endpoint}}</option>
</select>
</div>
</div>
<label *ngIf="noEndpointInfo.length != 0" class="colorRed alertLabel">{{noEndpointInfo | translate}}</label>
<span class="goLink" *ngIf="noEndpointInfo.length != 0" (click)="goRegistry()">{{'SIDE_NAV.SYSTEM_MGMT.REGISTRY' | translate}}</span>
</div>
<!--Trigger-->
<div class="form-group form-group-override">
<label class="form-group-label-override">{{'REPLICATION.TRIGGER_MODE' | translate}}</label>
<div formGroupName="trigger">
<!--on trigger-->
<div class="select floatSetPar">
<select id="ruleTrigger" formControlName="kind" (change)="selectTrigger($event)">
<option value="Manual">{{'REPLICATION.MANUAL' | translate}}</option>
<option value="Immediate">{{'REPLICATION.IMMEDIATE' | translate}}</option>
<option value="Scheduled">{{'REPLICATION.SCHEDULE' | translate}}</option>
</select>
</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)]="createEditRule.enable" name="enable" #enable="ngModel" [disabled]="untoggleable">
<label for="policy_enable"></label>
<!--on push-->
<div formGroupName="schedule_param">
<div class="select floatSet" [hidden]="!isScheduleOpt">
<select name="scheduleType" formControlName="type" (change)="selectSchedule($event)">
<option value="Daily">{{'REPLICATION.DAILY' | translate}}</option>
<option value="Weekly">{{'REPLICATION.WEEKLY' | translate}}</option>
</select>
</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="!isCreateEndpoint">
<select id="destination_name" [(ngModel)]="createEditRule.endpointId" name="endpointId" (change)="selectEndpoint()" [disabled]="testOngoing || readonly">
<option *ngFor="let t of endpoints" [value]="t.id" [selected]="t.id == createEditRule.endpointId">{{t.name}}</option>
</select>
</div>
<label class="col-md-8" *ngIf="isCreateEndpoint" for="destination_name" aria-haspopup="true" role="tooltip" [class.invalid]="endpointName.errors && (endpointName.dirty || endpointName.touched)"
class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
<input type="text" id="destination_name" [(ngModel)]="createEditRule.endpointName" name="endpointName" size="8" #endpointName="ngModel" value="" required>
<span class="tooltip-content" *ngIf="endpointName.errors && endpointName.errors.required && (endpointName.dirty || endpointName.touched)">
{{'REPLICATION.DESTINATION_NAME_IS_REQUIRED' | translate}}
</span>
</label>
<div class="checkbox-inline" *ngIf="showNewDestination">
<input type="checkbox" id="check_new" (click)="newEndpoint(checkedAddNew.checked)" #checkedAddNew [checked]="isCreateEndpoint" [disabled]="testOngoing || readonly">
<label for="check_new">{{'REPLICATION.NEW_DESTINATION' | translate}}</label>
<!--weekly-->
<span [hidden]="!weeklySchedule || !isScheduleOpt">on &nbsp;</span>
<div [hidden]="!weeklySchedule || !isScheduleOpt" class="select floatSet" style="width:104px">
<select name="scheduleDay" formControlName="weekday">
<option value="1">{{'WEEKLY.MONDAY' | translate}}</option>
<option value="2">{{'WEEKLY.TUESDAY' | translate}}</option>
<option value="3">{{'WEEKLY.WEDNESDAY' | translate}}</option>
<option value="4">{{'WEEKLY.THURSDAY' | translate}}</option>
<option value="5">{{'WEEKLY.FRIDAY' | translate}}</option>
<option value="6">{{'WEEKLY.SATURDAY' | translate}}</option>
<option value="7">{{'WEEKLY.SUNDAY' | translate}}</option>
</select>
</div>
<!--daily/time-->
<span [hidden]="!isScheduleOpt">at &nbsp;</span>
<input [hidden]="!isScheduleOpt" type="time" formControlName="offtime" required value="08:00" />
</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="tooltip tooltip-validation tooltip-sm tooltip-bottom-left">
<input type="text" id="destination_url" [disabled]="testOngoing" [readonly]="readonly || !isCreateEndpoint"
[(ngModel)]="createEditRule.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 || !isCreateEndpoint"
[(ngModel)]="createEditRule.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 || !isCreateEndpoint"
[(ngModel)]="createEditRule.password" size="20" name="password" #password="ngModel">
</div>
<div class="form-group">
<label for="destination_insecure" class="col-md-4 form-group-label-override">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
<clr-checkbox #insecure class="col-md-8" name="insecure" id="destination_insecure" [clrChecked]="!createEditRule.insecure" [clrDisabled]="readonly || !isCreateEndpoint || testOngoing" (clrCheckedChange)="setInsecureValue($event)">
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-7px;">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate}}</span>
</a>
</clr-checkbox>
</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>
</div>
<div style="width: 100%;" [hidden]="!isImmediate">
<clr-checkbox [clrChecked]="false" id="ruleDeletion" formControlName="replicate_deletion">
{{'REPLICATION.DELETE_REMOTE_IMAGES' | translate}}
</clr-checkbox>
</div>
<div style="width: 100%;" >
<clr-checkbox [clrChecked]="true" id="ruleExit" formControlName="replicate_existing_image_now">
{{'REPLICATION.REPLICATE_IMMEDIATE' | translate}}
</clr-checkbox>
</div>
</div>
<div style="display:block;text-align:center">
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
</div>
</section>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="testConnection()" [disabled]="testOngoing || endpointUrl.errors || connectAbled">{{'REPLICATION.TEST_CONNECTION' | translate}}</button>
<button type="button" class="btn btn-outline" [disabled]="btnAbled" (click)="onCancel()">{{'BUTTON.CANCEL' | translate }}</button>
<button type="submit" class="btn btn-primary" [disabled]="!ruleForm.form.valid || testOngoing || !editable" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
<button type="button" id="ruleBtnCancel" class="btn btn-outline" [disabled]="this.inProgress" (click)="onCancel()">{{ 'BUTTON.CANCEL' | translate }}</button>
<button type="submit" id="ruleBtnOk" class="btn btn-primary" (click)="onSubmit()" [disabled]="!ruleForm.valid || !isValid || !hasFormChange()">{{ 'BUTTON.SAVE' | translate }}</button>
</div>
</clr-modal>`;

View File

@ -1,4 +1,4 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
@ -25,52 +25,55 @@ import {
JobLogDefaultService
} from '../service/index';
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import {ProjectDefaultService, ProjectService} from "../service/project.service";
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
import {Project} from "../project-policy-config/project";
describe('CreateEditRuleComponent (inline template)', ()=>{
let mockRules: ReplicationRule[] = [
{
"id": 1,
"project_id": 1,
"project_name": "library",
"target_id": 1,
"target_name": "target_01",
"name": "sync_01",
"enabled": 0,
"description": "",
"cron_str": "",
"error_job_count": 2,
"deleted": 0
},
{
"id": 2,
"project_id": 1,
"project_name": "library",
"target_id": 3,
"target_name": "target_02",
"name": "sync_02",
"enabled": 1,
"description": "",
"cron_str": "",
"error_job_count": 1,
"deleted": 0
},
{
"id": 3,
"project_id": 1,
"project_name": "library",
"target_id": 2,
"target_name": "target_03",
"name": "sync_03",
"enabled": 0,
"description": "",
"cron_str": "",
"error_job_count": 0,
"deleted": 0
}
];
"projects": [{ "project_id": 1,
"owner_id": 0,
"name": 'project_01',
"creation_time": '',
"deleted": 0,
"owner_name": '',
"togglable": false,
"update_time": '',
"current_user_role_id": 0,
"repo_count": 0,
"has_project_admin_role": false,
"is_member": false,
"role_name": '',
"metadata": {
"public": '',
"enable_content_trust": '',
"prevent_vul": '',
"severity": '',
"auto_scan": '',
}
}],
"targets": [{
"id": 1,
"endpoint": "https://10.117.4.151",
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
}],
"trigger": {
"kind": "Manual",
"schedule_param": null
},
"filters": [],
"replicate_existing_image_now": false,
"replicate_deletion": false,
}]
let mockJobs: ReplicationJobItem[] = [
{
"id": 1,
@ -144,17 +147,68 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
let mockRule: ReplicationRule = {
"id": 1,
"project_id": 1,
"project_name": "library",
"target_id": 1,
"target_name": "target_01",
"name": "sync_01",
"enabled": 0,
"description": "",
"cron_str": "",
"error_job_count": 2,
"deleted": 0
};
"projects": [{ "project_id": 1,
"owner_id": 0,
"name": 'project_01',
"creation_time": '',
"deleted": 0,
"owner_name": '',
"togglable": false,
"update_time": '',
"current_user_role_id": 0,
"repo_count": 0,
"has_project_admin_role": false,
"is_member": false,
"role_name": '',
"metadata": {
"public": '',
"enable_content_trust": '',
"prevent_vul": '',
"severity": '',
"auto_scan": '',
}
}],
"targets": [{
"id": 1,
"endpoint": "https://10.117.4.151",
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
}],
"trigger": {
"kind": "Manual",
"schedule_param": null
},
"filters": [],
"replicate_existing_image_now": false,
"replicate_deletion": false,
}
let mockProjects: Project[] = [
{ "project_id": 1,
"owner_id": 0,
"name": 'project_01',
"creation_time": '',
"deleted": 0,
"owner_name": '',
"togglable": false,
"update_time": '',
"current_user_role_id": 0,
"repo_count": 0,
"has_project_admin_role": false,
"is_member": false,
"role_name": '',
"metadata": {
"public": '',
"enable_content_trust": '',
"prevent_vul": '',
"severity": '',
"auto_scan": '',
}
}];
let fixture: ComponentFixture<ReplicationComponent>;
let fixtureCreate: ComponentFixture<CreateEditRuleComponent>;
@ -164,7 +218,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
let replicationService: ReplicationService;
let endpointService: EndpointService;
let spyRules: jasmine.Spy;
let spyOneRule: jasmine.Spy;
@ -172,12 +226,11 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
let spyEndpoint: jasmine.Spy;
let config: IServiceConfig = {
replicationRuleEndpoint: '/api/policies/replication/testing',
replicationJobEndpoint: '/api/jobs/replication/testing',
targetBaseEndpoint: '/api/targets/testing'
};
beforeEach(async(()=>{
beforeEach(async(() =>{
TestBed.configureTestingModule({
imports: [
SharedModule,
@ -198,6 +251,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: EndpointService, useClass: EndpointDefaultService },
{ provide: ProjectService, useClass: ProjectDefaultService },
{ provide: JobLogService, useClass: JobLogDefaultService }
]
});
@ -205,28 +259,27 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
beforeEach(()=>{
fixture = TestBed.createComponent(ReplicationComponent);
fixtureCreate = TestBed.createComponent(CreateEditRuleComponent);
comp = fixture.componentInstance;
compCreate = fixtureCreate.componentInstance;
comp.projectId = 1;
comp.search.ruleId = 1;
replicationService = fixture.debugElement.injector.get(ReplicationService);
endpointService = fixtureCreate.debugElement.injector.get(EndpointService) ;
spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(Promise.resolve(mockRules));
spyOneRule = spyOn(replicationService, 'getReplicationRule').and.returnValue(Promise.resolve(mockRule));
spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJob));
fixture.detectChanges();
});
beforeEach(()=>{
fixtureCreate = TestBed.createComponent(CreateEditRuleComponent);
compCreate = fixtureCreate.componentInstance;
compCreate.projectId = 1;
endpointService = fixtureCreate.debugElement.injector.get(EndpointService);
spyEndpoint = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockEndpoints));
fixture.detectChanges();
});
it('Should open creation modal and load endpoints', async(()=>{

View File

@ -137,23 +137,13 @@ export class EndpointComponent implements OnInit, OnDestroy {
editTargets(targets: Endpoint[]) {
if (targets && targets.length === 1) {
let target= targets[0];
let target = targets[0];
let editable = true;
if (!target.id) {
return;
}
let id: number | string = target.id;
toPromise<ReplicationRule[]>(this.endpointService
.getEndpointWithReplicationRules(id))
.then(
rules => {
if (rules && rules.length > 0) {
rules.forEach((rule) => editable = (rule && rule.enabled !== 1));
}
this.createEditEndpointComponent.openCreateEditTarget(editable, id);
this.forceRefreshView(1000);
})
.catch(error => this.errorHandler.error(error));
this.createEditEndpointComponent.openCreateEditTarget(editable, id);
}
}

View File

@ -8,6 +8,7 @@ export * from './filter/index';
export * from './endpoint/index';
export * from './repository/index';
export * from './create-edit-endpoint/index';
export * from './create-edit-rule/index';
export * from './repository-stackview/index';
export * from './tag/index';
export * from './list-replication-rule/index';

View File

@ -1,7 +1,7 @@
export const LIST_REPLICATION_RULE_TEMPLATE: string = `
<div style="padding-bottom: 15px;">
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" [clDgRowSelection]="true">
<clr-dg-action-bar style="height:24px;" *ngIf="opereateAvailable || isSystemAdmin">
<clr-dg-action-bar style="height:24px;" *ngIf="isSystemAdmin">
<button type="button" class="btn btn-sm btn-secondary" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="editRule(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon>&nbsp;{{'REPLICATION.EDIT_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPLICATION.DELETE_POLICY' | translate}}</button>
@ -35,7 +35,6 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
<clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
<confirmation-dialog #toggleConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="toggleConfirm($event)"></confirmation-dialog>
<confirmation-dialog #deletionConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="deletionConfirm($event)"></confirmation-dialog>
</div>
`;

View File

@ -14,66 +14,91 @@ import { ErrorHandler } from '../error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
describe('ListReplicationRuleComponent (inline template)', ()=>{
let mockRules: ReplicationRule[] = [
{
"id": 1,
"project_id": 1,
"project_name": "library",
"target_id": 1,
"target_name": "target_01",
"projects": [{
"project_id": 33,
"owner_id": 1,
"name": "aeas",
"deleted": 0,
"togglable": false,
"current_user_role_id": 0,
"repo_count": 0,
"metadata": {
"public": false,
"enable_content_trust": "",
"prevent_vul": "",
"severity": "",
"auto_scan": ""},
"owner_name": "",
"creation_time": null,
"update_time": null,
"has_project_admin_role": true,
"is_member": true,
"role_name": ""
}],
"targets": [{
"endpoint": "",
"id": 0,
"insecure": false,
"name": "khans3",
"username": "",
"password": "",
"type": 0,
}],
"name": "sync_01",
"enabled": 0,
"description": "",
"cron_str": "",
"filters": null,
"trigger": {"kind": "Manual", "schedule_param": null},
"error_job_count": 2,
"deleted": 0
"replicate_deletion": false,
"replicate_existing_image_now": false,
},
{
"id": 2,
"project_id": 1,
"project_name": "library",
"target_id": 3,
"target_name": "target_02",
"name": "sync_02",
"enabled": 1,
"description": "",
"cron_str": "",
"error_job_count": 1,
"deleted": 0
},
{
"id": 3,
"project_id": 1,
"project_name": "library",
"target_id": 2,
"target_name": "target_03",
"name": "sync_03",
"enabled": 0,
"description": "",
"cron_str": "",
"error_job_count": 0,
"deleted": 0
}
"id": 2,
"projects": [{
"project_id": 33,
"owner_id": 1,
"name": "aeas",
"deleted": 0,
"togglable": false,
"current_user_role_id": 0,
"repo_count": 0,
"metadata": {
"public": false,
"enable_content_trust": "",
"prevent_vul": "",
"severity": "",
"auto_scan": ""},
"owner_name": "",
"creation_time": null,
"update_time": null,
"has_project_admin_role": true,
"is_member": true,
"role_name": ""
}],
"targets": [{
"endpoint": "",
"id": 0,
"insecure": false,
"name": "khans3",
"username": "",
"password": "",
"type": 0,
}],
"name": "sync_02",
"description": "",
"filters": null,
"trigger": {"kind": "Manual", "schedule_param": null},
"error_job_count": 2,
"replicate_deletion": false,
"replicate_existing_image_now": false,
},
];
let mockRule: ReplicationRule = {
"id": 1,
"project_id": 1,
"project_name": "library",
"target_id": 1,
"target_name": "target_01",
"name": "sync_01",
"enabled": 0,
"description": "",
"cron_str": "",
"error_job_count": 2,
"deleted": 0
};
let fixture: ComponentFixture<ListReplicationRuleComponent>;
let comp: ListReplicationRuleComponent;

View File

@ -60,9 +60,8 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
@Input() isSystemAdmin: boolean;
@Input() selectedId: number | string;
@Input() withReplicationJob: boolean;
@Input() readonly: boolean;
@Input() loading: boolean = false;
@Input() loading = false;
@Output() reload = new EventEmitter<boolean>();
@Output() selectOne = new EventEmitter<ReplicationRule>();
@ -100,10 +99,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
setInterval(() => ref.markForCheck(), 500);
}
public get opereateAvailable(): boolean {
return !this.readonly && !this.projectId ? true : false;
}
trancatedDescription(desc: string): string {
if (desc.length > 35 ) {
return desc.substr(0, 35);
@ -135,7 +130,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
retrieveRules(ruleName: string = ''): void {
this.loading = true;
this.selectedRow = null;
/*this.selectedRow = null;*/
toPromise<ReplicationRule[]>(this.replicationService
.getReplicationRules(this.projectId, ruleName))
.then(rules => {
@ -156,36 +151,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
});
}
filterRuleStatus(status: string) {
if (status === 'all') {
this.changedRules = this.rules;
} else {
this.changedRules = this.rules.filter(policy => policy.enabled === +status);
}
}
toggleConfirm(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.TOGGLE_CONFIRM &&
message.state === ConfirmationState.CONFIRMED) {
this.batchDelectionInfos = [];
let rule: ReplicationRule = message.data;
let initBatchMessage = new BatchInfo ();
initBatchMessage.name = rule.name;
this.batchDelectionInfos.push(initBatchMessage);
if (rule) {
rule.enabled = rule.enabled === 0 ? 1 : 0;
toPromise<any>(this.replicationService
.enableReplicationRule(rule.id || '', rule.enabled))
.then(() =>
this.translateService.get('REPLICATION.TOGGLED_SUCCESS')
.subscribe(res => this.batchDelectionInfos[0].status = res))
.catch(error => this.batchDelectionInfos[0].status = error);
}
}
}
replicateRule(rules: ReplicationRule[]): void {
this.replicateManual.emit(rules);
}
@ -216,17 +181,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
this.editOne.emit(rule);
}
toggleRule(rule: ReplicationRule) {
let toggleConfirmMessage: ConfirmationMessage = new ConfirmationMessage(
rule.enabled === 1 ? 'REPLICATION.TOGGLE_DISABLE_TITLE' : 'REPLICATION.TOGGLE_ENABLE_TITLE',
rule.enabled === 1 ? 'REPLICATION.CONFIRM_TOGGLE_DISABLE_POLICY' : 'REPLICATION.CONFIRM_TOGGLE_ENABLE_POLICY',
rule.name || '',
rule,
ConfirmationTargets.TOGGLE_CONFIRM
);
this.toggleConfirmDialog.open(toggleConfirmMessage);
}
jobList(id: string | number): Promise<void> {
let ruleData: ReplicationJobItem[];
this.canDeleteRule = true;

View File

@ -1,18 +1,18 @@
export class Project {
project_id: number;
owner_id: number;
owner_id?: number;
name: string;
creation_time: Date | string;
deleted: number;
owner_name: string;
togglable: boolean;
update_time: Date | string;
current_user_role_id: number;
repo_count: number;
has_project_admin_role: boolean;
is_member: boolean;
role_name: string;
metadata: {
creation_time?: Date | string;
deleted?: number;
owner_name?: string;
togglable?: boolean;
update_time?: Date | string;
current_user_role_id?: number;
repo_count?: number;
has_project_admin_role?: boolean;
is_member?: boolean;
role_name?: string;
metadata?: {
public: string | boolean;
enable_content_trust: string | boolean;
prevent_vul: string | boolean;

View File

@ -11,7 +11,7 @@ export const REPLICATION_TEMPLATE: string = `
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<hbr-list-replication-rule #listReplicationRule [readonly]="readonly" [projectId]="projectId" [isSystemAdmin]="isSystemAdmin" (replicateManual)=replicateManualRule($event) (selectOne)="selectOneRule($event)" (hideJobs)="hideJobs()" (openNewRule)="openModal()" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
<hbr-list-replication-rule #listReplicationRule [projectId]="projectId" [isSystemAdmin]="isSystemAdmin" (replicateManual)=replicateManualRule($event) (selectOne)="selectOneRule($event)" (hideJobs)="hideJobs()" (openNewRule)="openModal()" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" style="padding-left:0px;">
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
@ -73,5 +73,6 @@ export const REPLICATION_TEMPLATE: string = `
</div>
</div>
<job-log-viewer #replicationLogViewer></job-log-viewer>
<hbr-create-edit-rule [projectId]="projectId" [projectName]="projectName" (goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule>
<confirmation-dialog #replicationConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmReplication($event)"></confirmation-dialog>
</div>`;

View File

@ -12,7 +12,7 @@ import { DatePickerComponent } from '../datetime-picker/datetime-picker.componen
import { DateValidatorDirective } from '../datetime-picker/date-validator.directive';
import { FilterComponent } from '../filter/filter.component';
import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
import { ReplicationRule, ReplicationJob, Endpoint } from '../service/interface';
import {ReplicationRule, ReplicationJob, Endpoint} from '../service/interface';
import { ErrorHandler } from '../error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
@ -20,49 +20,92 @@ import { ReplicationService, ReplicationDefaultService } from '../service/replic
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index';
import {Project} from "../project-policy-config/project";
import {ProjectDefaultService, ProjectService} from "service/project.service";
describe('Replication Component (inline template)', ()=>{
describe('Replication Component (inline template)', () => {
let mockRules: ReplicationRule[] = [
{
"id": 1,
"project_id": 1,
"project_name": "library",
"target_id": 1,
"target_name": "target_01",
"name": "sync_01",
"enabled": 0,
"description": "",
"cron_str": "",
"error_job_count": 2,
"deleted": 0
},
{
"id": 2,
"project_id": 1,
"project_name": "library",
"target_id": 3,
"target_name": "target_02",
"name": "sync_02",
"enabled": 1,
"description": "",
"cron_str": "",
"error_job_count": 1,
"deleted": 0
},
{
"id": 3,
"project_id": 1,
"project_name": "library",
"target_id": 2,
"target_name": "target_03",
"name": "sync_03",
"enabled": 0,
"description": "",
"cron_str": "",
"error_job_count": 0,
"deleted": 0
}
{
"id": 1,
"projects": [{
"project_id": 33,
"owner_id": 1,
"name": "aeas",
"deleted": 0,
"togglable": false,
"current_user_role_id": 0,
"repo_count": 0,
"metadata": {
"public": false,
"enable_content_trust": "",
"prevent_vul": "",
"severity": "",
"auto_scan": ""},
"owner_name": "",
"creation_time": null,
"update_time": null,
"has_project_admin_role": true,
"is_member": true,
"role_name": ""
}],
"targets": [{
"id": 1,
"endpoint": "https://10.117.4.151",
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
}],
"name": "sync_01",
"description": "",
"filters": null,
"trigger": {"kind": "Manual", "schedule_param": null},
"error_job_count": 2,
"replicate_deletion": false,
"replicate_existing_image_now": false,
},
{
"id": 2,
"projects": [{
"project_id": 33,
"owner_id": 1,
"name": "aeas",
"deleted": 0,
"togglable": false,
"current_user_role_id": 0,
"repo_count": 0,
"metadata": {
"public": false,
"enable_content_trust": "",
"prevent_vul": "",
"severity": "",
"auto_scan": ""},
"owner_name": "",
"creation_time": null,
"update_time": null,
"has_project_admin_role": true,
"is_member": true,
"role_name": ""
}],
"targets": [{
"id": 1,
"endpoint": "https://10.117.4.151",
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
}],
"name": "sync_02",
"description": "",
"filters": null,
"trigger": {"kind": "Manual", "schedule_param": null},
"error_job_count": 2,
"replicate_deletion": false,
"replicate_existing_image_now": false,
}
];
let mockJobs: ReplicationJobItem[] = [
@ -81,7 +124,7 @@ describe('Replication Component (inline template)', ()=>{
"repository": "library/mysql",
"policy_id": 1,
"operation": "transfer",
"update_time": new Date("2017-05-27 12:20:33"),
"update_time": new Date("2017-05-27 12:20:33"),
"tags": null
},
{
@ -95,71 +138,66 @@ describe('Replication Component (inline template)', ()=>{
}
];
let mockEndpoints: Endpoint[] = [
{
"id": 1,
"endpoint": "https://10.117.4.151",
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
},
{
"id": 2,
"endpoint": "https://10.117.5.142",
"name": "target_02",
"username": "AAA",
"password": "",
"insecure": false,
"type": 0
},
];
let mockProjects: Project[] = [
{ "project_id": 1,
"owner_id": 0,
"name": 'project_01',
"creation_time": '',
"deleted": 0,
"owner_name": '',
"togglable": false,
"update_time": '',
"current_user_role_id": 0,
"repo_count": 0,
"has_project_admin_role": false,
"is_member": false,
"role_name": '',
"metadata": {
"public": '',
"enable_content_trust": '',
"prevent_vul": '',
"severity": '',
"auto_scan": '',
}
}];
let mockJob: ReplicationJob = {
metadata: {xTotalCount: 3},
data: mockJobs
};
let mockEndpoints: Endpoint[] = [
{
"id": 1,
"endpoint": "https://10.117.4.151",
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
},
{
"id": 2,
"endpoint": "https://10.117.5.142",
"name": "target_02",
"username": "AAA",
"password": "",
"insecure": false,
"type": 0
},
{
"id": 3,
"endpoint": "https://101.1.11.111",
"name": "target_03",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
},
{
"id": 4,
"endpoint": "http://4.4.4.4",
"name": "target_04",
"username": "",
"password": "",
"insecure": false,
"type": 0
}
];
let mockRule: ReplicationRule = {
"id": 1,
"project_id": 1,
"project_name": "library",
"target_id": 1,
"target_name": "target_01",
"name": "sync_01",
"enabled": 0,
"description": "",
"cron_str": "",
"error_job_count": 2,
"deleted": 0
};
let fixture: ComponentFixture<ReplicationComponent>;
let fixtureCreate: ComponentFixture<CreateEditRuleComponent>;
let comp: ReplicationComponent;
let compCreate: CreateEditRuleComponent;
let replicationService: ReplicationService;
let endpointService: EndpointService;
let spyRules: jasmine.Spy;
let spyJobs: jasmine.Spy;
let spyEndpoint: jasmine.Spy;
let deGrids: DebugElement[];
let deRules: DebugElement;
@ -194,23 +232,29 @@ describe('Replication Component (inline template)', ()=>{
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: EndpointService, useClass: EndpointDefaultService },
{ provide: ProjectService, useClass: ProjectDefaultService },
{ provide: JobLogService, useClass: JobLogDefaultService }
]
});
}));
beforeEach(()=>{
beforeEach(() => {
fixture = TestBed.createComponent(ReplicationComponent);
fixtureCreate = TestBed.createComponent(CreateEditRuleComponent);
comp = fixture.componentInstance;
compCreate = fixtureCreate.componentInstance;
comp.projectId = 1;
comp.search.ruleId = 1;
replicationService = fixture.debugElement.injector.get(ReplicationService);
endpointService = fixtureCreate.debugElement.injector.get(EndpointService);
spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(Promise.resolve(mockRules));
spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJob));
spyEndpoint = spyOn(endpointService, 'getEndpoints').and.returnValues(Promise.resolve(mockEndpoints));
fixture.detectChanges();
fixture.whenStable().then(()=>{
fixture.detectChanges();
@ -221,6 +265,7 @@ describe('Replication Component (inline template)', ()=>{
});
});
it('Should load replication rules', async(()=>{
fixture.detectChanges();
fixture.whenStable().then(()=>{

View File

@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, ViewChild, Input, Output, OnDestroy, EventEmitter } from '@angular/core';
import { ResponseOptions, RequestOptions } from '@angular/http';
import { NgModel } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
@ -23,7 +21,7 @@ import { ErrorHandler } from '../error-handler/error-handler';
import { ReplicationService } from '../service/replication.service';
import { RequestQueryParams } from '../service/RequestQueryParams';
import { ReplicationRule, ReplicationJob, Endpoint, ReplicationJobItem } from '../service/interface';
import { ReplicationRule, ReplicationJob, ReplicationJobItem } from '../service/interface';
import {
toPromise,
@ -89,13 +87,14 @@ export class SearchOption {
export class ReplicationComponent implements OnInit, OnDestroy {
@Input() projectId: number | string;
@Input() projectName: string;
@Input() isSystemAdmin: boolean;
@Input() withReplicationJob: boolean;
@Input() readonly: boolean;
@Output() redirect = new EventEmitter<ReplicationRule>();
@Output() openCreateRule = new EventEmitter<any>();
@Output() openEdit = new EventEmitter<string | number>();
@Output() goToRegistry = new EventEmitter<any>();
search: SearchOption = new SearchOption();
@ -106,7 +105,6 @@ export class ReplicationComponent implements OnInit, OnDestroy {
currentJobStatus: { key: string, description: string };
changedRules: ReplicationRule[];
initSelectedId: number | string;
rules: ReplicationRule[];
loading: boolean;
@ -121,8 +119,8 @@ export class ReplicationComponent implements OnInit, OnDestroy {
@ViewChild(ListReplicationRuleComponent)
listReplicationRule: ListReplicationRuleComponent;
/* @ViewChild(CreateEditRuleComponent)
createEditPolicyComponent: CreateEditRuleComponent;*/
@ViewChild(CreateEditRuleComponent)
createEditPolicyComponent: CreateEditRuleComponent;
@ViewChild("replicationLogViewer")
replicationLogViewer: JobLogViewerComponent;
@ -164,20 +162,22 @@ export class ReplicationComponent implements OnInit, OnDestroy {
}
}
// open replication rule
openModal(): void {
this.openCreateRule.emit();
this.createEditPolicyComponent.openCreateEditRule();
}
// edit replication rule
openEditRule(rule: ReplicationRule) {
if (rule) {
let editable = true;
if (rule.enabled === 1) {
editable = false;
}
this.openEdit.emit(rule.id);
this.createEditPolicyComponent.openCreateEditRule(rule.id);
}
}
goRegistry(): void {
this.goToRegistry.emit();
}
//Server driven data loading
clrLoadJobs(state: State): void {
if (!state || !state.page || !this.search.ruleId) {
@ -209,6 +209,12 @@ export class ReplicationComponent implements OnInit, OnDestroy {
}
this.jobsLoading = true;
//Do filtering and sorting
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);
this.jobsLoading = false;
toPromise<ReplicationJob>(this.replicationService
.getJobs(this.search.ruleId, params))
.then(

View File

@ -1,3 +1,4 @@
import {Project} from "../project-policy-config/project";
/**
* The base interface contains the general properties
*
@ -83,18 +84,40 @@ export interface Endpoint extends Base {
*
* @export
* @interface ReplicationRule
* @interface Filter
* @interface Trigger
*/
export interface ReplicationRule extends Base {
project_id: number | string;
project_name: string;
target_id: number | string;
target_name: string;
enabled: number;
description?: string;
cron_str?: string;
start_time?: Date;
error_job_count?: number;
deleted: number;
[key: string]: any;
id?: number;
name: string;
description: string;
projects: Project[];
targets: Endpoint[] ;
trigger: Trigger ;
filters: Filter[] ;
replicate_existing_image_now?: boolean;
replicate_deletion?: boolean;
}
export class Filter {
kind: string;
pattern: string;
constructor(kind: string, pattern: string) {
this.kind = kind;
this.pattern = pattern;
}
}
export class Trigger {
kind: string;
schedule_param: any | {
[key: string]: any | any[];
};
constructor(kind: string, param: any | { [key: string]: any | any[]; }) {
this.kind = kind;
this.schedule_param = param;
}
}
/**
@ -115,7 +138,7 @@ export interface ReplicationJob {
* @interface ReplicationJob
*/
export interface ReplicationJobItem extends Base {
[key: string]: any | any[]
[key: string]: any | any[];
status: string;
repository: string;
policy_id: number;
@ -151,7 +174,7 @@ export interface AccessLog {
* @interface AccessLogItem
*/
export interface AccessLogItem {
[key: string]: any | any[]
[key: string]: any | any[];
log_id: number;
project_id: number;
repo_name: string;

View File

@ -6,7 +6,8 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { Project } from '../project-policy-config/project';
import { ProjectPolicy } from '../project-policy-config/project-policy-config.component';
import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "../utils";
import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS, buildHttpRequestOptions} from "../utils";
import {RequestQueryParams} from "./RequestQueryParams";
/**
* Define the service methods to handle the Prject related things.
@ -38,6 +39,8 @@ export abstract class ProjectService {
* @memberOf EndpointService
*/
abstract updateProjectPolicy(projectId: number | string, projectPolicy: ProjectPolicy): Observable<any> | Promise<any> | any;
abstract listProjects(name: string, isPublic: number, page?: number, pageSize?: number): Observable<Project[]> | Promise<Project[]> | Project[];
}
/**
@ -68,6 +71,27 @@ export class ProjectDefaultService extends ProjectService {
.catch(error => Observable.throw(error));
}
listProjects(name: string, isPublic: number, page?: number, pageSize?: number): Observable<Project[]> | Promise<Project[]> | Project[] {
let params = new RequestQueryParams();
if (page && pageSize) {
params.set('page', page + '');
params.set('page_size', pageSize + '');
}
if (name && name.trim() !== "") {
params.set('name', name);
}
if (isPublic !== undefined) {
params.set('public', '' + isPublic);
}
// let options = new RequestOptions({ headers: this.getHeaders, search: params });
return this.http
.get(`/api/projects`, buildHttpRequestOptions(params))
.map(response => response.json())
.catch(error => Observable.throw(error));
}
public updateProjectPolicy(projectId: number | string, projectPolicy: ProjectPolicy): any {
return this.http
.put(`/api/projects/${projectId}`, { 'metadata': {

View File

@ -1,6 +1,6 @@
import { Observable } from 'rxjs/Observable';
import { RequestQueryParams } from './RequestQueryParams';
import { ReplicationJob, ReplicationRule, ReplicationJobItem } from './interface';
import {ReplicationJob, ReplicationRule, ReplicationJobItem} from './interface';
import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of';
import { Http, RequestOptions } from '@angular/http';
@ -62,7 +62,7 @@ export abstract class ReplicationService {
*
* @memberOf ReplicationService
*/
abstract updateReplicationRule(replicationRule: ReplicationRule): Observable<any> | Promise<any> | any;
abstract updateReplicationRule(id: number, rep: ReplicationRule): Observable<any> | Promise<any> | any;
/**
* Delete the specified replication rule.
@ -159,7 +159,7 @@ export class ReplicationDefaultService extends ReplicationService {
//Private methods
//Check if the rule object is valid
_isValidRule(rule: ReplicationRule): boolean {
return rule !== undefined && rule != null && rule.name !== undefined && rule.name.trim() !== '' && rule.target_id !== 0;
return rule !== undefined && rule != null && rule.name !== undefined && rule.name.trim() !== '' && rule.targets.length !== 0;
}
public getReplicationRules(projectId?: number | string, ruleName?: string, queryParams?: RequestQueryParams): Observable<ReplicationRule[]> | Promise<ReplicationRule[]> | ReplicationRule[] {
@ -177,7 +177,7 @@ export class ReplicationDefaultService extends ReplicationService {
return this.http.get(this._ruleBaseUrl, buildHttpRequestOptions(queryParams)).toPromise()
.then(response => response.json() as ReplicationRule[])
.catch(error => Promise.reject(error))
.catch(error => Promise.reject(error));
}
public getReplicationRule(ruleId: number | string): Observable<ReplicationRule> | Promise<ReplicationRule> | ReplicationRule {
@ -201,13 +201,13 @@ export class ReplicationDefaultService extends ReplicationService {
.catch(error => Promise.reject(error));
}
public updateReplicationRule(replicationRule: ReplicationRule): Observable<any> | Promise<any> | any {
if (!this._isValidRule(replicationRule) || !replicationRule.id) {
public updateReplicationRule(id: number, rep: ReplicationRule): Observable<any> | Promise<any> | any {
if (!this._isValidRule(rep)) {
return Promise.reject('Bad argument');
}
let url: string = `${this._ruleBaseUrl}/${replicationRule.id}`;
return this.http.put(url, JSON.stringify(replicationRule), HTTP_JSON_OPTIONS).toPromise()
let url = `${this._ruleBaseUrl}/${id}`;
return this.http.put(url, JSON.stringify(rep), HTTP_JSON_OPTIONS).toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
@ -298,7 +298,7 @@ export class ReplicationDefaultService extends ReplicationService {
return Promise.reject('Bad argument');
}
let logUrl: string = `${this._jobBaseUrl}/${jobId}/log`;
let logUrl = `${this._jobBaseUrl}/${jobId}/log`;
return this.http.get(logUrl, HTTP_GET_OPTIONS).toPromise()
.then(response => response.text())
.catch(error => Promise.reject(error));

View File

@ -2,8 +2,8 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpModule, Http } from '@angular/http';
import { ClarityModule } from 'clarity-angular';
import { FormsModule } from '@angular/forms';
import { TranslateModule, TranslateLoader, TranslateService, MissingTranslationHandler } from '@ngx-translate/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule, TranslateLoader, MissingTranslationHandler } from '@ngx-translate/core';
import { MyMissingTranslationHandler } from '../i18n/missing-trans.handler';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { TranslatorJsonLoader } from '../i18n/local-json.loader';
@ -41,6 +41,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) {
CommonModule,
HttpModule,
FormsModule,
ReactiveFormsModule,
ClipboardModule,
CookieModule.forRoot(),
ClarityModule.forRoot(),
@ -60,6 +61,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) {
CommonModule,
HttpModule,
FormsModule,
ReactiveFormsModule,
CookieModule,
ClipboardModule,
ClarityModule,

View File

@ -31,7 +31,7 @@
"clarity-icons": "^0.10.17",
"clarity-ui": "^0.10.17",
"core-js": "^2.4.1",
"harbor-ui": "0.6.45",
"harbor-ui": "0.6.46",
"intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0",

View File

@ -50,8 +50,6 @@ import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deac
import { MemberGuard } from './shared/route/member-guard-activate.service';
import { TagDetailPageComponent } from './repository/tag-detail/tag-detail-page.component';
import { ReplicationRuleComponent} from "./replication/replication-rule/replication-rule.component";
import {LeavingNewRuleRouteDeactivate} from "./shared/route/leaving-new-rule-deactivate.service";
import { LeavingRepositoryRouteDeactivate } from './shared/route/leaving-repository-deactivate.service';
const harborRoutes: Routes = [
@ -92,20 +90,6 @@ const harborRoutes: Routes = [
canActivate: [SystemAdminGuard],
canActivateChild: [SystemAdminGuard],
},
{
path: 'replications/:id/rule',
component: ReplicationRuleComponent,
canActivate: [SystemAdminGuard],
canActivateChild: [SystemAdminGuard],
canDeactivate: [LeavingNewRuleRouteDeactivate]
},
{
path: 'replications/new-rule',
component: ReplicationRuleComponent,
canActivate: [SystemAdminGuard],
canActivateChild: [SystemAdminGuard],
canDeactivate: [LeavingNewRuleRouteDeactivate]
},
{
path: 'tags/:id/:repo',
component: TagRepositoryComponent,

View File

@ -1,3 +1,3 @@
<div style="margin-top: 24px;">
<hbr-replication [readonly]="true" #replicationView [projectId]="projectIdentify" [isSystemAdmin]="isSystemAdmin" [withReplicationJob]='true' (openCreateRule)="openCreatePage()" (openEdit)="openEditPage($event)"></hbr-replication>
<hbr-replication #replicationView [projectId]="projectIdentify" [projectName]="projectName" [isSystemAdmin]="isSystemAdmin" [withReplicationJob]='true' ></hbr-replication>
</div>

View File

@ -15,6 +15,8 @@ import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import { ReplicationComponent } from 'harbor-ui';
import {SessionService} from "../shared/session.service";
import {Project} from "../project/project";
import {ProjectService} from "../project/project.service";
@Component({
selector: 'replicaton',
@ -23,13 +25,25 @@ import {SessionService} from "../shared/session.service";
export class ReplicationPageComponent implements OnInit, AfterViewInit {
projectIdentify: string | number;
@ViewChild("replicationView") replicationView: ReplicationComponent;
projectName: string;
constructor(private route: ActivatedRoute,
private router: Router,
private proService: ProjectService,
private session: SessionService) { }
ngOnInit(): void {
this.projectIdentify = +this.route.snapshot.parent.params['id'];
this.proService.listProjects("", undefined).toPromise()
.then(response => {
let projects = response.json() as Project[];
if (projects.length) {
let project = projects.find(data => data.project_id === this.projectIdentify);
if (project) {
this.projectName = project.name;
}
}
});
}
public get isSystemAdmin(): boolean {
@ -37,14 +51,6 @@ export class ReplicationPageComponent implements OnInit, AfterViewInit {
return account != null && account.has_admin_role > 0;
}
openEditPage(id: number): void {
this.router.navigate(['harbor', 'replications', id, 'rule', { projectId: this.projectIdentify}]);
}
openCreatePage(): void {
this.router.navigate(['harbor', 'replications', 'new-rule', { projectId: this.projectIdentify}] );
}
ngAfterViewInit(): void {
let isCreated: boolean = this.route.snapshot.queryParams['is_create'];
if (isCreated) {

View File

@ -1,5 +0,0 @@
.datagrid .datagrid-head{border: 0;}
.option-right{ position: absolute; right: 30px; top: 55px;}
:host >>> .datagrid-head{height: 0;border-width: 0;}
:host >>> .datagrid-scroll-wrapper .datagrid{margin-top: 0;}
.modal-body{height: 30em; overflow-y: auto; margin-top: 20px;}

View File

@ -1,33 +0,0 @@
<clr-modal [(clrModalOpen)]="ismodelOpen" [clrModalClosable]="false">
<h3 class="modal-title">{{'PROJECT.ALL_PROJECTS' | translate}}</h3>
<inline-alert class="modal-title" ></inline-alert>
<div class="modal-body">
<div class="option-right">
<div class="select" style="float: left; left:-6px; top:8px;">
<select (change)="doFilterProject()" [(ngModel)]="selecteType">
<option value="0" [selected]="currentFilteredType === 0">{{projectTypes[0] | translate}}</option>
<option value="1">{{projectTypes[1] | translate}}</option>
<option value="2">{{projectTypes[2] | translate}}</option>
</select>
</div>
<hbr-filter [withDivider]="true" filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchProject($event)" [currentValue]="projectName"></hbr-filter>
<span class="refresh-btn" (click)="refresh()">
<clr-icon shape="refresh"></clr-icon>
</span>
</div>
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedProject">
<clr-dg-row *ngFor="let project of projects; let i = index" [clrDgItem]="project">
<clr-dg-cell>{{project.name}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span> {{pagination.totalItems }} {{'PROJECT.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="currentPage" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="closeModel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="!selectedProject" (click)="oKModel()">{{'BUTTON.OK' | translate}}</button>
</div>
</clr-modal>

View File

@ -1,181 +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,
Input,
ChangeDetectionStrategy,
ChangeDetectorRef,
OnDestroy, EventEmitter
} from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';
import { SessionService } from '../../../shared/session.service';
import { SearchTriggerService } from '../../../base/global-search/search-trigger.service';
import { ProjectTypes, RoleInfo} from '../../../shared/shared.const';
import { CustomComparator, doFiltering, doSorting, calculatePage } from '../../../shared/shared.utils';
import { Comparator, State } from 'clarity-angular';
import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service';
import { StatisticHandler } from '../../../shared/statictics/statistic-handler.service';
import { Subscription } from 'rxjs/Subscription';
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 {ProjectService} from "../../../project/project.service";
import {Project} from "../../../project/project";
@Component({
selector: 'list-project-model',
templateUrl: 'list-project-model.component.html',
styleUrls: ['list-project-model.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListProjectModelComponent {
projectTypes = ProjectTypes;
loading: boolean = true;
projects: Project[] = [];
filteredType: number = 0;//All projects
searchKeyword: string = "";
ismodelOpen: boolean ;
currentFilteredType: number = 0;//all projects
projectName: string = "";
selectedProject: Project;
roleInfo = RoleInfo;
repoCountComparator: Comparator<Project> = new CustomComparator<Project>("repo_count", "number");
timeComparator: Comparator<Project> = new CustomComparator<Project>("creation_time", "date");
accessLevelComparator: Comparator<Project> = new CustomComparator<Project>("public", "number");
roleComparator: Comparator<Project> = new CustomComparator<Project>("current_user_role_id", "number");
currentPage: number = 1;
totalCount: number = 0;
pageSize: number = 10;
currentState: State;
@Output() selectedPro = new EventEmitter<Project>();
constructor(
private session: SessionService,
private router: Router,
private searchTrigger: SearchTriggerService,
private proService: ProjectService,
private msgHandler: MessageHandlerService,
private statisticHandler: StatisticHandler,
private deletionDialogService: ConfirmationDialogService,
private ref: ChangeDetectorRef) {
}
get selecteType(): number {
return this.currentFilteredType;
}
set selecteType(_project: number) {
this.currentFilteredType = _project;
if (window.sessionStorage) {
window.sessionStorage['projectTypeValue'] = _project;
}
}
clrLoad(state: State) {
this.selectedProject = null;
//Keep state for future filtering and sorting
this.currentState = state;
let pageNumber: number = calculatePage(state);
if (pageNumber <= 0) { pageNumber = 1; }
this.loading = true;
let passInFilteredType: number = undefined;
if (this.filteredType > 0) {
passInFilteredType = this.filteredType - 1;
}
this.proService.listProjects(this.searchKeyword, passInFilteredType, pageNumber, this.pageSize).toPromise()
.then(response => {
//Get total count
if (response.headers) {
let xHeader: string = response.headers.get("X-Total-Count");
if (xHeader) {
this.totalCount = parseInt(xHeader, 0);
}
}
this.projects = response.json() as Project[];
//Do customising filtering and sorting
this.projects = doFiltering<Project>(this.projects, state);
this.projects = doSorting<Project>(this.projects, state);
this.loading = false;
})
.catch(error => {
this.loading = false;
this.msgHandler.handleError(error);
});
//Force refresh view
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 3000);
}
openModel(): void {
this.selectedProject = null;
this.ismodelOpen = true;
//Force refresh view
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 2000);
}
refresh(): void {
this.currentPage = 1;
this.filteredType = 0;
this.searchKeyword = '';
this.reload();
}
doFilterProject(): void {
this.currentPage = 1;
this.filteredType = this.selecteType;
this.reload();
}
doSearchProject(proName: string): void {
this.projectName = proName;
this.currentPage = 1;
this.searchKeyword = proName;
this.reload();
}
reload(): void {
let st: State = this.currentState;
if (!st) {
st = {
page: {}
};
}
st.page.from = 0;
st.page.to = this.pageSize - 1;
st.page.size = this.pageSize;
this.clrLoad(st);
}
oKModel() {
this.ismodelOpen = false;
this.selectedPro.emit(this.selectedProject);
}
closeModel(): void {
this.ismodelOpen = false;
}
}

View File

@ -1,590 +0,0 @@
import {Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, AfterViewInit} from '@angular/core';
import {ProjectService} from '../../project/project.service';
import {Project} from '../../project/project';
import {ActivatedRoute, Router} from '@angular/router';
import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms";
import {ReplicationRuleServie} from "./replication-rule.service";
import {MessageHandlerService} from "../../shared/message-handler/message-handler.service";
import {Target, Filter, ReplicationRule} from "./replication-rule";
import {ConfirmationDialogService} from "../../shared/confirmation-dialog/confirmation-dialog.service";
import { ConfirmationTargets, ConfirmationState } from '../../shared/shared.const';
import {Subscription} from "rxjs/Subscription";
import {ConfirmationMessage} from "../../shared/confirmation-dialog/confirmation-message";
import {Subject} from "rxjs/Subject";
import {ListProjectModelComponent} from "./list-project-model/list-project-model.component";
import {toPromise, isEmptyObject, compareValue} from "harbor-ui/src/utils";
import {CreateEditEndpointComponent} from "harbor-ui/src/create-edit-endpoint/create-edit-endpoint.component";
const ONE_HOUR_SECONDS: number = 3600;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@Component ({
selector: 'repliction-rule',
templateUrl: 'replication-rule.html',
styleUrls: ['replication-rule.css']
})
export class ReplicationRuleComponent implements OnInit, OnDestroy {
_localTime: Date = new Date();
policyId: number;
projectId: number;
targetList: Target[] = [];
isFilterHide: boolean = false;
weeklySchedule: boolean;
isScheduleOpt: boolean;
isImmediate: boolean = false;
noProjectInfo: string = "";
noSelectedProject: boolean = true;
noSelectedEndpoint: boolean = true;
filterCount: number = 0;
selectedprojectList: Project[] = [];
triggerNames: string[] = ['Manual', 'Immediate', 'Scheduled'];
scheduleNames: string[] = ['Daily', 'Weekly'];
weekly: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
filterSelect: string[] = ['repository', 'tag'];
ruleNameTooltip: string = 'TOOLTIP.EMPTY';
headerTitle: string = 'REPLICATION.ADD_POLICY';
filterListData: {[key: string]: any}[] = [];
inProgress: boolean = false;
inNameChecking: boolean = false;
isRuleNameExist: boolean = false;
isSubmitOver: boolean = false;
nameChecker: Subject<string> = new Subject<string>();
confirmSub: Subscription;
ruleForm: FormGroup;
copyUpdateForm: ReplicationRule;
emptyEndpoint = new Target();
@ViewChild(ListProjectModelComponent)
projectListModel: ListProjectModelComponent;
@ViewChild(CreateEditEndpointComponent)
createEditEndpointComponent: CreateEditEndpointComponent;
baseFilterData(name: string, option: string[], state: boolean) {
return {
name: name,
options: option,
state: state,
isValid: true
};
}
constructor(public projectService: ProjectService,
private router: Router,
private fb: FormBuilder,
private repService: ReplicationRuleServie,
private route: ActivatedRoute,
private msgHandler: MessageHandlerService,
private confirmService: ConfirmationDialogService,
public ref: ChangeDetectorRef) {
this.createForm();
Promise.all([this.repService.getEndpoints(), this.repService.listProjects()])
.then(res => {
if (!res[0]) {
this.noSelectedEndpoint = true;
}else {
this.targetList = res[0];
if (!this.policyId) {
res[0].unshift(this.emptyEndpoint);
this.setTarget([res[0][0]]);
}
}
if (!res[1]) {
this.noProjectInfo = 'REPLICATION.NO_PROJECT_INFO';
}else {
if (!this.policyId && !this.projectId) {
this.setProject([res[1][0]]);
}
if (!this.policyId && this.projectId) {
this.setProject( res[1].filter(rule => rule.project_id === this.projectId));
this.noSelectedProject = false;
}
}
if (!this.policyId) {
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
}
});
}
ngOnInit(): void {
this.policyId = +this.route.snapshot.params['id'];
this.projectId = +this.route.snapshot.params['projectId'];
if (this.policyId) {
this.headerTitle = 'REPLICATION.EDIT_POLICY_TITLE';
this.repService.getReplicationRule(this.policyId)
.then((response) => {
this.copyUpdateForm = Object.assign({}, response);
// set filter value is [] if callback fiter value is null.
this.copyUpdateForm.filters = response.filters ? response.filters : [];
this.updateForm(response);
}).catch(error => {
this.msgHandler.handleError(error);
});
}
this.nameChecker.debounceTime(500).distinctUntilChanged().subscribe((ruleName: string) => {
this.isRuleNameExist = false;
this.inNameChecking = true;
toPromise<ReplicationRule[]>(this.repService.getReplicationRules(0, ruleName))
.then(response => {
if (response.some(rule => rule.name === ruleName)) {
this.ruleNameTooltip = 'TOOLTIP.RULE_USER_EXISTING';
this.isRuleNameExist = true;
}
this.inNameChecking = false;
}).catch(() => {
this.inNameChecking = false;
});
});
}
ngOnDestroy(): void {
if (this.confirmSub) {
this.confirmSub.unsubscribe();
}
if (this.nameChecker) {
this.nameChecker.unsubscribe();
}
}
get isVaild() {
return !(this.isRuleNameExist || this.noSelectedProject || this.noSelectedEndpoint || this.inProgress || this.isSubmitOver);
}
createForm() {
this.ruleForm = this.fb.group({
name: ['', Validators.required],
description: '',
projects: this.fb.array([]),
targets: this.fb.array([]),
trigger: this.fb.group({
kind: this.triggerNames[0],
schedule_param: this.fb.group({
type: this.scheduleNames[0],
weekday: 1,
offtime: '08:00'
}),
}),
filters: this.fb.array([]),
replicate_existing_image_now: true,
replicate_deletion: false
});
}
updateForm(rule: ReplicationRule): void {
rule.trigger = this.updateTrigger(rule.trigger);
this.ruleForm.reset({
name: rule.name,
description: rule.description,
trigger: rule.trigger,
replicate_existing_image_now: rule.replicate_existing_image_now,
replicate_deletion: rule.replicate_deletion
});
this.setProject(rule.projects);
this.noSelectedProject = false;
this.setTarget(rule.targets);
this.noSelectedEndpoint = false;
if (rule.filters) {
this.setFilter(rule.filters);
this.updateFilter(rule.filters);
}
// Force refresh view
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 2000);
}
get projects(): FormArray {
return this.ruleForm.get('projects') as FormArray;
}
setProject(projects: Project[]) {
const projectFGs = projects.map(project => this.fb.group(project));
const projectFormArray = this.fb.array(projectFGs);
this.ruleForm.setControl('projects', projectFormArray);
}
get filters(): FormArray {
return this.ruleForm.get('filters') as FormArray;
}
setFilter(filters: Filter[]) {
const filterFGs = filters.map(filter => this.fb.group(filter));
const filterFormArray = this.fb.array(filterFGs);
this.ruleForm.setControl('filters', filterFormArray);
}
get targets(): FormArray {
return this.ruleForm.get('targets') as FormArray;
}
setTarget(targets: Target[]) {
const targetFGs = targets.map(target => this.fb.group(target));
const targetFormArray = this.fb.array(targetFGs);
this.ruleForm.setControl('targets', targetFormArray);
}
initFilter(name: string) {
return this.fb.group({
kind: name,
pattern: ['', Validators.required]
});
}
filterChange($event: any) {
if ($event && $event.target['value']) {
let id: number = $event.target.id;
let name: string = $event.target.name;
let value: string = $event.target['value'];
this.filterListData.forEach((data, index) => {
if (index === +id) {
data.name = $event.target.name = value;
}else {
data.options.splice(data.options.indexOf(value), 1);
}
if (data.options.indexOf(name) === -1) {
data.options.push(name);
}
});
}
}
targetChange($event: any) {
if ($event && $event.target && event.target['value']) {
if ($event.target['value'] === '-1') {
this.noSelectedEndpoint = true;
return;
}
let selecedTarget: Target = this.targetList.find(target => target.id === +$event.target['value']);
this.setTarget([selecedTarget]);
this.noSelectedEndpoint = false;
}
}
openProjectModel(): void {
this.projectListModel.openModel();
}
selectedProject(project: Project): void {
if (!project) {
this.noSelectedProject = true;
}else {
this.noSelectedProject = false;
this.setProject([project]);
}
}
addNewFilter(): void {
if (this.filterCount === 0) {
this.filterListData.push(this.baseFilterData(this.filterSelect[0], this.filterSelect.slice(), true));
this.filters.push(this.initFilter(this.filterSelect[0]));
}else {
let nameArr: string[] = this.filterSelect.slice();
this.filterListData.forEach(data => {
nameArr.splice(nameArr.indexOf(data.name), 1);
});
// when add a new filter,the filterListData should change the options
this.filterListData.filter((data) => {
data.options.splice(data.options.indexOf(nameArr[0]), 1);
});
this.filterListData.push(this.baseFilterData(nameArr[0], nameArr, true));
this.filters.push(this.initFilter(nameArr[0]));
}
this.filterCount += 1;
if (this.filterCount >= this.filterSelect.length) {
this.isFilterHide = true;
}
}
// delete a filter
deleteFilter(i: number): void {
if (i || i === 0) {
let delfilter = this.filterListData.splice(i, 1)[0];
if (this.filterCount === this.filterSelect.length) {
this.isFilterHide = false;
}
this.filterCount -= 1;
if (this.filterListData.length) {
let optionVal = delfilter.name;
this.filterListData.filter(data => {
if (data.options.indexOf(optionVal) === -1) {
data.options.push(optionVal);
}
});
}
const control = <FormArray>this.ruleForm.controls['filters'];
control.removeAt(i);
}
}
selectTrigger($event: any): void {
if ($event && $event.target && $event.target['value']) {
let val: string = $event.target['value'];
if (val === this.triggerNames[2]) {
this.isScheduleOpt = true;
this.isImmediate = false;
}
if (val === this.triggerNames[1]) {
this.isScheduleOpt = false;
this.isImmediate = true;
}
if (val === this.triggerNames[0]) {
this.isScheduleOpt = false;
this.isImmediate = false;
}
}
}
// Replication Schedule select value exchange
selectSchedule($event: any): void {
if ($event && $event.target && $event.target['value']) {
switch ($event.target['value']) {
case this.scheduleNames[1]:
this.weeklySchedule = true;
this.ruleForm.patchValue({
trigger: {
schedule_param: {
weekday: 1,
}
}
})
break;
case this.scheduleNames[0]:
this.weeklySchedule = false;
break;
}
}
}
checkRuleName(): void {
let ruleName: string = this.ruleForm.controls['name'].value;
if (ruleName) {
this.nameChecker.next(ruleName);
} else {
this.ruleNameTooltip = 'TOOLTIP.EMPTY';
}
}
updateFilter(filters: any) {
let opt: string[] = this.filterSelect.slice();
filters.forEach((filter: any) => {
opt.splice(opt.indexOf(filter.kind), 1);
})
filters.forEach((filter: any) => {
let option: string [] = opt.slice();
option.unshift(filter.kind);
this.filterListData.push(this.baseFilterData(filter.kind, option, true));
});
this.filterCount = filters.length;
if (filters.length === this.filterSelect.length) {
this.isFilterHide = true;
}
}
updateTrigger(trigger: any) {
if (trigger['schedule_param']) {
this.isScheduleOpt = true;
this.isImmediate = false;
trigger['schedule_param']['offtime'] = this.getOfftime(trigger['schedule_param']['offtime']);
if (trigger['schedule_param']['weekday']) {
this.weeklySchedule = true;
}else {
// set default
trigger['schedule_param']['weekday'] = 1;
}
}else {
if (trigger['kind'] === this.triggerNames[0]) {
this.isImmediate = false;
}
trigger['schedule_param'] = { type: this.scheduleNames[0],
weekday: this.weekly[0],
offtime: '08:00'};
}
return trigger;
}
setTriggerVaule(trigger: any) {
if (!this.isScheduleOpt) {
delete trigger['schedule_param'];
return trigger;
}else {
if (!this.weeklySchedule) {
delete trigger['schedule_param']['weekday'];
}else {
trigger['schedule_param']['weekday'] = +trigger['schedule_param']['weekday'];
}
trigger['schedule_param']['offtime'] = this.setOfftime(trigger['schedule_param']['offtime']);
return trigger;
}
}
public hasFormChange(): boolean {
return !isEmptyObject(this.getChanges());
}
onSubmit() {
// add new Replication rule
this.inProgress = true;
let copyRuleForm: ReplicationRule = this.ruleForm.value;
copyRuleForm.trigger = this.setTriggerVaule(copyRuleForm.trigger);
if (!this.policyId) {
this.repService.createReplicationRule(copyRuleForm)
.then(() => {
this.msgHandler.showSuccess('REPLICATION.CREATED_SUCCESS');
this.inProgress = false;
this.isSubmitOver = true;
setTimeout(() => {
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
if (this.projectId) {
this.router.navigate(['harbor/projects', this.projectId, 'replications']);
}else {
this.router.navigate(['/harbor/replications']);
}
}, 2000);
}).catch((error: any) => {
this.inProgress = false;
this.msgHandler.handleError(error);
});
} else {
this.repService.updateReplicationRule(this.policyId, this.ruleForm.value)
.then(() => {
this.msgHandler.showSuccess('REPLICATION.UPDATED_SUCCESS');
this.inProgress = false;
this.isSubmitOver = true;
setTimeout(() => {
this.copyUpdateForm = Object.assign({}, this.ruleForm.value);
if (this.projectId) {
this.router.navigate(['harbor/projects', this.projectId, 'replications']);
}else {
this.router.navigate(['/harbor/replications']);
}
}, 2000);
}).catch((error: any) => {
this.inProgress = false;
this.msgHandler.handleError(error);
});
}
}
openModal() {
this.createEditEndpointComponent.openCreateEditTarget(true);
}
reload($event: boolean) {
if ($event) {
Promise.all([this.repService.getEndpoints()]).then(res => {
this.targetList = res[0];
this.setTarget([this.targetList[this.targetList.length - 1]]);
this.noSelectedEndpoint = false;
});
}
}
onCancel(): void {
this.router.navigate(['/harbor/replications']);
}
// UTC time
public getOfftime(daily_time: any): string {
let timeOffset: number = 0; // seconds
if (daily_time && typeof daily_time === 'number') {
timeOffset = +daily_time;
}
// Convert to current time
let timezoneOffset: number = this._localTime.getTimezoneOffset();
// Local time
timeOffset = timeOffset - timezoneOffset * 60;
if (timeOffset < 0) {
timeOffset = timeOffset + ONE_DAY_SECONDS;
}
if (timeOffset >= ONE_DAY_SECONDS) {
timeOffset -= ONE_DAY_SECONDS;
}
// To time string
let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS);
let minutes: number = Math.floor((timeOffset - hours * ONE_HOUR_SECONDS) / 60);
let timeStr: string = '' + hours;
if (hours < 10) {
timeStr = '0' + timeStr;
}
if (minutes < 10) {
timeStr += ':0';
} else {
timeStr += ':';
}
timeStr += minutes;
return timeStr;
}
public setOfftime(v: string) {
if (!v || v === '') {
return;
}
let values: string[] = v.split(':');
if (!values || values.length !== 2) {
return;
}
let hours: number = +values[0];
let minutes: number = +values[1];
// Convert to UTC time
let timezoneOffset: number = this._localTime.getTimezoneOffset();
let utcTimes: number = hours * ONE_HOUR_SECONDS + minutes * 60;
utcTimes += timezoneOffset * 60;
if (utcTimes < 0) {
utcTimes += ONE_DAY_SECONDS;
}
if (utcTimes >= ONE_DAY_SECONDS) {
utcTimes -= ONE_DAY_SECONDS;
}
return utcTimes;
}
backReplication(): void {
this.router.navigate(['/harbor/replications']);
}
backProjectReplication(): void {
this.router.navigate(['harbor/projects', this.projectId, 'replications']);
}
getChanges(): { [key: string]: any | any[] } {
let changes: { [key: string]: any | any[] } = {};
let ruleValue: { [key: string]: any | any[] } = this.ruleForm.value;
if (!ruleValue || !this.copyUpdateForm) {
return changes;
}
for (let prop in ruleValue) {
let field = this.copyUpdateForm[prop];
if (!compareValue(field, ruleValue[prop])) {
changes[prop] = ruleValue[prop];
//Number
if (typeof field === "number") {
changes[prop] = +changes[prop];
}
//Trim string value
if (typeof field === "string") {
changes[prop] = ('' + changes[prop]).trim();
}
}
}
return changes;
}
}

View File

@ -1,43 +0,0 @@
/**
* Created by pengf on 9/28/2017.
*/
.select{
width: 186px;
}
.select .optionMore{
background-color: #bfbaba;
height: 1.6em;
font-size: 1.2em;
cursor: pointer;
text-align: center;
}
.hideFilter{ display: none;}
h4{
color: #666;
}
.colorRed{color: red;}
.colorRed a{text-decoration: underline;color: #007CBB;}
label:first-child {
font-size: 15px;
left: -10px !important;
}
.inputWidth{width: 310px;}
.endpointSelect{ width: 290px; margin-right: 34px;}
.filterSelect{width: 350px;}
.filterSelect clr-icon{margin-left: 10px;}
.filterSelect label{width: 175px;}
.filterSelect label input{width: 100%;}
.cursor{cursor: pointer;}
.pull-left{float: left;}
.padLeft0{padding-left: 0;}
.floatSet {display: inline-block; width: 120px;margin-right: 10px;}
.form-group{ min-height: 36px;}
.projectInput{float: left;}
.projectInput input{background-color: white;}
.switchIcon{width:20px;height:20px; margin-top: 10px;margin-left: 10px; cursor: pointer;}
.addEndpoint{ margin-top: .25em !important;}
.shadow{position: absolute;top: 8px;}
.is-solid{cursor: pointer;}

View File

@ -1,128 +0,0 @@
<div>
<a class="cursor" *ngIf="!projectId" (click)="backReplication()">< {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a>
<a class="cursor" *ngIf="projectId" (click)="backProjectReplication()"><{{'SIDE_NAV.PROJECTS' | translate}} &nbsp; {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate | lowercase}}</a>
<h1 class="sub-header-title">{{headerTitle | translate}}</h1>
<form [formGroup]="ruleForm" novalidate>
<section class="form-block">
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'REPLICATION.NAME' | translate}}<span class="colorRed">*</span></label>
<label class="col-md-8" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
[class.invalid]='(ruleForm.controls.name.touched && ruleForm.controls.name.invalid) || isRuleNameExist'>
<input type="text" id="ruleName" class="inputWidth" required maxlength="255" formControlName="name" #ruleName (keyup)='checkRuleName()' autocomplete="off">
<span class="tooltip-content">{{ruleNameTooltip | translate}}</span>
</label><span class="spinner spinner-inline spinner-pos" [hidden]="!inNameChecking"></span>
</div>
<!--Description-->
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'REPLICATION.DESCRIPTION' | translate}}</label>
<textarea type="text" id="ruleDescription" class="inputWidth" row= 3; formControlName="description"></textarea>
</div>
<!--Projects-->
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'REPLICATION.SOURCE' | translate}}&nbsp;{{'PROJECT.PROJECTS' | translate | lowercase}}<span class="colorRed">*</span></label>
<div formArrayName="projects" [style.visibility]="noSelectedProject?'hidden':'visible'">
<div class="projectInput" *ngFor="let project of projects.controls; let i= index" [formGroupName]="i">
<input formControlName="name" type="text" class="inputWidth" disabled value="name">
</div>
</div>
<clr-icon *ngIf="!(noProjectInfo.length !=0 || projectId)" shape="search" class="is-solid switchIcon" (click)="openProjectModel()"></clr-icon>
<label *ngIf="noProjectInfo.length != 0" class="colorRed">{{noProjectInfo | translate}}</label>
<div class="shadow" [hidden]="!noSelectedProject || noProjectInfo.length != 0"><input type="text" class="inputWidth" disabled ></div>
</div>
<!--images/Filter-->
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'REPLICATION.SOURCE_IMAGES_FILTER' | translate}}</label>
<div formArrayName="filters">
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index" [formGroupName]="i">
<div>
<div class="select floatSet">
<select formControlName="kind" (change)="filterChange($event)" id="{{i}}" name="{{filterListData[i]?.name}}">
<option *ngFor="let filter of filterListData[i]?.options;" value="{{filter}}">{{filter}}</option>
</select>
</div>
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
[class.invalid]='ruleForm.controls.filters.controls[i].controls.pattern.touched && ruleForm.controls.filters.controls[i].controls.pattern.invalid'>
<input type="text" #filterValue required size="14" formControlName="pattern">
<span class="tooltip-content">{{'TOOLTIP.EMPTY' | translate}}</span>
</label>
<clr-icon shape="times-circle" class="is-solid" (click)="deleteFilter(i)"></clr-icon>
</div>
</div>
</div>
<clr-icon shape="plus-circle" class="is-solid" [hidden]="isFilterHide" (click)="addNewFilter()" style="margin-top: 11px;"></clr-icon>
</div>
<!--Targets-->
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'DESTINATION.ENDPOINT' | translate}} <span class="colorRed">*</span></label>
<div formArrayName="targets">
<div class="select endpointSelect pull-left" *ngFor="let target of targets.controls; let i= index" [formGroupName]="i">
<select id="ruleTarget " class="inputWidth" (change)="targetChange($event)" formControlName="id">
<option *ngFor="let target of targetList" value="{{target.id}}">{{target.name}}-{{target.endpoint}}</option>
</select>
</div>
<button class="btn btn-info btn-sm addEndpoint" (click)="openModal()"><clr-icon shape="plus"></clr-icon>&nbsp;{{'REPLICATION.NEW' | translate}}</button>
</div>
</div>
<!--Trigger-->
<div class="form-group">
<label class="col-md-4 form-group-label-override">{{'REPLICATION.TRIGGER_MODE' | translate}}</label>
<div formGroupName="trigger">
<!--on trigger-->
<div class="select floatSet">
<select id="ruleTrigger" formControlName="kind" (change)="selectTrigger($event)">
<option value="Manual">{{'REPLICATION.MANUAL' | translate}}</option>
<option value="Immediate">{{'REPLICATION.IMMEDIATE' | translate}}</option>
<option value="Scheduled">{{'REPLICATION.SCHEDULE' | translate}}</option>
</select>
</div>
<!--on push-->
<div formGroupName="schedule_param">
<div class="select floatSet" [hidden]="!isScheduleOpt">
<select name="scheduleType" formControlName="type" (change)="selectSchedule($event)">
<option value="Daily">{{'REPLICATION.DAILY' | translate}}</option>
<option value="Weekly">{{'REPLICATION.WEEKLY' | translate}}</option>
</select>
</div>
<!--weekly-->
<span [hidden]="!weeklySchedule || !isScheduleOpt">on &nbsp;&nbsp;</span>
<div [hidden]="!weeklySchedule || !isScheduleOpt" class="select floatSet">
<select name="scheduleDay" formControlName="weekday">
<option value="1">{{'WEEKLY.MONDAY' | translate}}</option>
<option value="2">{{'WEEKLY.TUESDAY' | translate}}</option>
<option value="3">{{'WEEKLY.WEDNESDAY' | translate}}</option>
<option value="4">{{'WEEKLY.THURSDAY' | translate}}</option>
<option value="5">{{'WEEKLY.FRIDAY' | translate}}</option>
<option value="6">{{'WEEKLY.SATURDAY' | translate}}</option>
<option value="7">{{'WEEKLY.SUNDAY' | translate}}</option>
</select>
</div>
<!--daily/time-->
<span [hidden]="!isScheduleOpt">at &nbsp;&nbsp;</span>
<input [hidden]="!isScheduleOpt" type="time" formControlName="offtime" required value="08:00" />
</div>
</div>
<div style="width: 100%;" [hidden]="!isImmediate">
<clr-checkbox [clrChecked]="false" id="ruleDeletion" formControlName="replicate_deletion">
{{'REPLICATION.DELETE_REMOTE_IMAGES' | translate}}
</clr-checkbox>
</div>
<div style="width: 100%;" >
<clr-checkbox [clrChecked]="true" id="ruleExit" formControlName="replicate_existing_image_now">
{{'REPLICATION.REPLICATE_IMMEDIATE' | translate}}
</clr-checkbox>
</div>
</div>
<div>
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
<br>
<button type="submit" id="ruleBtnOk" class="btn btn-primary" (click)="onSubmit()" [disabled]="!ruleForm.valid || !isVaild || !hasFormChange()">{{ 'BUTTON.SAVE' | translate }}</button>
<button type="button" id="ruleBtnCancel" class="btn btn-outline" [disabled]="this.inProgress || this.isSubmitOver" (click)="onCancel()">{{ 'BUTTON.CANCEL' | translate }}</button>
</div><!-- [disabled]="!ruleForm.valid"-->
</section>
</form>
<list-project-model (selectedPro)="selectedProject($event)"></list-project-model>
<hbr-create-edit-endpoint (reload)="reload($event)"></hbr-create-edit-endpoint>
</div>

View File

@ -1,75 +0,0 @@
/**
* Created by pengf on 12/5/2017.
*/
import {Injectable} from "@angular/core";
import {Http, RequestOptions, Headers, URLSearchParams} from "@angular/http";
import {Observable} from "rxjs/Observable";
import {ReplicationRule, Target} from "./replication-rule";
import {HTTP_GET_OPTIONS, HTTP_JSON_OPTIONS} from "../../shared/shared.utils";
import {Project} from "../../project/project";
@Injectable()
export class ReplicationRuleServie {
headers = new Headers({'Content-type': 'application/json'});
options = new RequestOptions({'headers': this.headers});
baseurl = '/api/policies/replication';
targetUrl= '/api/targets';
constructor(private http: Http) {}
public createReplicationRule(replicationRule: ReplicationRule): Observable<any> | Promise<any> | any {
/*if (!this._isValidRule(replicationRule)) {
return Promise.reject('Bad argument');
}*/
return this.http.post(this.baseurl, JSON.stringify(replicationRule), this.options).toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
public getReplicationRules(projectId?: number | string, ruleName?: string): Promise<ReplicationRule[]> | ReplicationRule[] {
let queryParams = new URLSearchParams();
if (projectId) {
queryParams.set('project_id', '' + projectId);
}
if (ruleName) {
queryParams.set('name', ruleName);
}
return this.http.get(this.baseurl, {search: queryParams}).toPromise()
.then(response => response.json() as ReplicationRule[])
.catch(error => Promise.reject(error));
}
public getReplicationRule(policyId: number): Promise<ReplicationRule> {
let url: string = `${this.baseurl}/${policyId}`;
return this.http.get(url, HTTP_GET_OPTIONS).toPromise()
.then(response => response.json() as ReplicationRule)
.catch(error => Promise.reject(error));
}
public getEndpoints(): Promise<Target[]> | Target[] {
return this.http
.get(this.targetUrl)
.toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
}
public listProjects(): Promise<Project[]> | Project[] {
return this.http.get(`/api/projects`, HTTP_GET_OPTIONS).toPromise()
.then(response => response.json())
.catch(error => Promise.reject(error));
}
public updateReplicationRule(id: number, rep: {[key: string]: any | any[] }): Observable<any> | Promise<any> | any {
let url: string = `${this.baseurl}/${id}`;
return this.http.put(url, JSON.stringify(rep), HTTP_JSON_OPTIONS).toPromise()
.then(response => response)
.catch(error => Promise.reject(error));
}
}

View File

@ -1,60 +0,0 @@
import {Project} from "../../project/project";
/**
* Created by pengf on 12/7/2017.
*/
export class Target {
id: number;
endpoint: string;
name: string;
username: string;
password: string;
type: number;
insecure: true;
creation_time: string;
update_time: string;
constructor() {
this.id = -1;
this.endpoint = "";
this.name = "";
this.username = "";
this.password = "";
this.type = 0;
this.insecure = true;
this.creation_time = "";
this.update_time = "";
}
}
export class Filter {
kind: string;
pattern: string;
constructor(kind: string, pattern: string) {
this.kind = kind;
this.pattern = pattern;
}
}
export class Trigger {
kind: string;
schedule_param: any | {
[key: string]: any | any[];
};
constructor(kind: string, param: any | { [key: string]: any | any[]; }) {
this.kind = kind;
this.schedule_param = param;
}
}
export interface ReplicationRule {
id?: number;
name: string;
description: string;
projects: Project[];
targets: Target[] ;
trigger: Trigger ;
filters: Filter[] ;
replicate_existing_image_now?: boolean;
replicate_deletion?: boolean;
}

View File

@ -20,10 +20,7 @@ import { TotalReplicationPageComponent } from './total-replication/total-replica
import { DestinationPageComponent } from './destination/destination-page.component';
import { SharedModule } from '../shared/shared.module';
import {ReplicationRuleComponent} from "./replication-rule/replication-rule.component";
import {ReactiveFormsModule} from "@angular/forms";
import {ReplicationRuleServie} from "./replication-rule/replication-rule.service";
import {ListProjectModelComponent} from "./replication-rule/list-project-model/list-project-model.component";
@NgModule({
imports: [
@ -36,15 +33,11 @@ import {ListProjectModelComponent} from "./replication-rule/list-project-model/l
ReplicationManagementComponent,
TotalReplicationPageComponent,
DestinationPageComponent,
ReplicationRuleComponent,
ListProjectModelComponent,
],
exports: [
ReplicationPageComponent,
DestinationPageComponent,
TotalReplicationPageComponent,
ReplicationRuleComponent,
],
providers: [ReplicationRuleServie]
]
})
export class ReplicationModule { }

View File

@ -1,4 +1,4 @@
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</h2>
<div style="margin-top: 24px;">
<hbr-replication [readonly]="false" [withReplicationJob]='true' [isSystemAdmin]="isSystemAdmin" (openCreateRule)="openCreatePage()" (openEdit)="openEditPage($event)" (redirect)="customRedirect($event)"></hbr-replication>
<hbr-replication [withReplicationJob]='true' [isSystemAdmin]="isSystemAdmin" (goToRegistry)="goRegistry()" (redirect)="customRedirect($event)"></hbr-replication>
</div>

View File

@ -13,9 +13,10 @@
// limitations under the License.
import { Component } from '@angular/core';
import {Router,ActivatedRoute} from "@angular/router";
import {ReplicationRule} from "../replication-rule/replication-rule";
import {Router, ActivatedRoute} from "@angular/router";
import {SessionService} from "../../shared/session.service";
import {ReplicationRule} from "harbor-ui";
@Component({
selector: 'total-replication',
@ -31,17 +32,12 @@ export class TotalReplicationPageComponent {
this.router.navigate(['../projects', rule.projects[0].project_id, 'replications'], { relativeTo: this.activeRoute });
}
}
goRegistry(): void {
this.router.navigate(['../registries'], { relativeTo: this.activeRoute });
}
public get isSystemAdmin(): boolean {
let account = this.session.getCurrentUser();
return account != null && account.has_admin_role > 0;
}
openEditPage(id: number): void {
this.router.navigate([id, 'rule'], { relativeTo: this.activeRoute });
}
openCreatePage(): void {
this.router.navigate(['new-rule'], { relativeTo: this.activeRoute });
}
}

View File

@ -32,7 +32,7 @@
<div class="form-group form-group-override">
<label for="realname" class="required form-group-label-override">{{'PROFILE.FULL_NAME' | translate}}</label>
<label for="realname" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='getValidationState("realname")'>
<input type="text" name="realname" #fullNameInput="ngModel" [(ngModel)]="newUser.realname" required maxLengthExt="20" id="realname" size="30"
<input type="text" name="realname" #fullNameInput="ngModel" [(ngModel)]="newUser.realname" required maxLengthExt="80" id="realname" size="30"
(input)='handleValidation("realname", false)'
(blur)='handleValidation("realname", true)'>
<span class="tooltip-content">

View File

@ -1,65 +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 {
CanDeactivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '@angular/router';
import { ConfirmationDialogService } from '../confirmation-dialog/confirmation-dialog.service';
import { ConfigurationComponent } from '../../config/config.component';
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
import { ConfirmationState, ConfirmationTargets } from '../shared.const';
import {ReplicationRuleComponent} from "../../replication/replication-rule/replication-rule.component";
@Injectable()
export class LeavingNewRuleRouteDeactivate implements CanDeactivate<ReplicationRuleComponent> {
constructor(
private router: Router,
private confirmation: ConfirmationDialogService) { }
canDeactivate(
replicateRule: ReplicationRuleComponent,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Promise<boolean> | boolean {
//Confirmation before leaving config route
return new Promise((resolve, reject) => {
if (replicateRule && replicateRule.hasFormChange()) {
let msg: ConfirmationMessage = new ConfirmationMessage(
"CONFIG.LEAVING_CONFIRMATION_TITLE",
"CONFIG.LEAVING_CONFIRMATION_SUMMARY",
'',
{},
ConfirmationTargets.CONFIG_ROUTE
);
this.confirmation.openComfirmDialog(msg);
return this.confirmation.confirmationConfirm$.subscribe(msg => {
if (msg && msg.source === ConfirmationTargets.CONFIG_ROUTE) {
if (msg.state === ConfirmationState.CONFIRMED) {
return resolve(true);
} else {
return resolve(false);//Prevent leading route
}
} else {
return resolve(true);//Should go on
}
});
} else {
return resolve(true);
}
});
}
}

View File

@ -58,7 +58,6 @@ import {
ErrorHandler,
HarborLibraryModule
} from 'harbor-ui';
import {LeavingNewRuleRouteDeactivate} from "./route/leaving-new-rule-deactivate.service";
import { LeavingRepositoryRouteDeactivate } from './route/leaving-repository-deactivate.service';
const uiLibConfig: IServiceConfig = {
@ -125,7 +124,6 @@ const uiLibConfig: IServiceConfig = {
AuthCheckGuard,
SignInGuard,
LeavingConfigRouteDeactivate,
LeavingNewRuleRouteDeactivate,
LeavingRepositoryRouteDeactivate,
MemberGuard,
MessageHandlerService,

View File

@ -167,6 +167,7 @@ export class UserComponent implements OnInit, OnDestroy {
//Filter items by keywords
doFilter(terms: string): void {
this.selectedRow = [];
this.currentTerm = terms;
this.originalUsers.then(users => {
if (terms.trim() === "") {

View File

@ -322,8 +322,8 @@
"PLACEHOLDER": "We couldn't find any replication rules!",
"JOB_PLACEHOLDER": "We couldn't find any replication jobs!",
"JOB_LOG_VIEWER": "View Replication Job Log",
"NO_ENDPOINT_INFO": "Please go to registries and add an endpoint first",
"NO_PROJECT_INFO": "Please go to projects and add a project name first",
"NO_ENDPOINT_INFO": "Please add an endpoint first",
"NO_PROJECT_INFO": "Please add a project name first",
"SOURCE_IMAGES_FILTER": "Source images filter",
"SCHEDULE": "Scheduled",
"MANUAL": "Manual",

View File

@ -322,8 +322,8 @@
"PLACEHOLDER": "We couldn't find any replication rules!",
"JOB_PLACEHOLDER": "We couldn't find any replication jobs!",
"JOB_LOG_VIEWER": "View Replication Job Log",
"NO_ENDPOINT_INFO": "Please go to registries and add an endpoint first",
"NO_PROJECT_INFO": "Please go to projects and add a project name first",
"NO_ENDPOINT_INFO": "Please add an endpoint first",
"NO_PROJECT_INFO": "Please add a project name first",
"SOURCE_IMAGES_FILTER": "Source images filter",
"SCHEDULE": "Scheduled",
"MANUAL": "Manual",

View File

@ -322,8 +322,8 @@
"PLACEHOLDER": "未发现任何复制规则!",
"JOB_PLACEHOLDER": "未发现任何复制任务!",
"JOB_LOG_VIEWER": "查看复制任务日志",
"NO_ENDPOINT_INFO": "请先添加目标",
"NO_PROJECT_INFO": "请先去项目添加一个新的项目名称",
"NO_ENDPOINT_INFO": "请先添加一个目标",
"NO_PROJECT_INFO": "请先添加一个项目名称",
"SOURCE_IMAGES_FILTER": "源镜像过滤器",
"SCHEDULE": "定时",
"MANUAL": "手动",