mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-07 15:31:26 +01:00
Merge pull request #4354 from pengpengshui/reptolib
Modify replication rule from page to dialog #4296
This commit is contained in:
commit
7bf277df6e
@ -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**
|
||||
|
@ -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;}
|
||||
`;
|
@ -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}} {{'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 </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 </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>`;
|
||||
|
@ -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(()=>{
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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> {{'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> {{'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> {{'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>
|
||||
`;
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>`;
|
@ -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(()=>{
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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': {
|
||||
|
@ -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));
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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>
|
@ -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) {
|
||||
|
@ -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;}
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;}
|
@ -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}} {{'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}} {{'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> {{'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 </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 </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>
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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 { }
|
@ -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>
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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() === "") {
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "手动",
|
||||
|
Loading…
Reference in New Issue
Block a user