mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-12 14:08:23 +02:00
Merge pull request #7177 from zhoumeina/replication_ng
Merge create replication policy code
This commit is contained in:
commit
761004f98d
@ -21,12 +21,12 @@ import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
|
||||
describe("CreateEditEndpointComponent (inline template)", () => {
|
||||
let mockData: Endpoint = {
|
||||
id: 1,
|
||||
endpoint: "https://10.117.4.151",
|
||||
url: "https://10.117.4.151",
|
||||
name: "target_01",
|
||||
username: "admin",
|
||||
password: "",
|
||||
insecure: false,
|
||||
type: 0
|
||||
type: "harbor"
|
||||
};
|
||||
|
||||
let comp: CreateEditEndpointComponent;
|
||||
|
@ -98,12 +98,12 @@ export class CreateEditEndpointComponent
|
||||
|
||||
initEndpoint(): Endpoint {
|
||||
return {
|
||||
endpoint: "",
|
||||
url: "",
|
||||
name: "",
|
||||
username: "",
|
||||
password: "",
|
||||
insecure: false,
|
||||
type: 0
|
||||
type: ""
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
<h3 class="modal-title">{{headerTitle | translate}}</h3>
|
||||
<hbr-inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></hbr-inline-alert>
|
||||
<div class="modal-body modal-body-height">
|
||||
<clr-alert [hidden]='!deletedLabelCount' [clrAlertType]="'alert-warning'" [clrAlertSizeSmall]="true"
|
||||
[clrAlertClosable]="false" [(clrAlertClosed)]="alertClosed">
|
||||
<clr-alert [hidden]='!deletedLabelCount' [clrAlertType]="'alert-warning'" [clrAlertSizeSmall]="true" [clrAlertClosable]="false"
|
||||
[(clrAlertClosed)]="alertClosed">
|
||||
<div class="alert-item">
|
||||
<span class="alert-text">{{deletedLabelInfo}}</span>
|
||||
<div class="alert-actions">
|
||||
@ -15,10 +15,9 @@
|
||||
<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) || !isRuleNameValid'>
|
||||
<input type="text" id="ruleName" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" class="inputWidth" required
|
||||
maxlength="255" formControlName="name" #ruleName (keyup)='checkRuleName()' autocomplete="off">
|
||||
<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) || !isRuleNameValid'>
|
||||
<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>
|
||||
@ -28,29 +27,42 @@
|
||||
<label class="form-group-label-override">{{'REPLICATION.DESCRIPTION' | translate}}</label>
|
||||
<textarea type="text" id="ruleDescription" class="inputWidth" row=3 formControlName="description"></textarea>
|
||||
</div>
|
||||
<!--Projects-->
|
||||
<!-- replication mode -->
|
||||
<div class="form-group form-group-override">
|
||||
<label class="form-group-label-override required">{{'REPLICATION.SOURCE_PROJECT' | translate}}</label>
|
||||
<div formArrayName="projects">
|
||||
<div class="projectInput inputWidth" *ngFor="let project of projects.controls; let i= index"
|
||||
[formGroupName]="i" (mouseleave)="leaveInput()">
|
||||
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
|
||||
[class.invalid]='noProjectInfo'>
|
||||
<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>
|
||||
<span class="tooltip-content">{{noProjectInfo | translate}}</span>
|
||||
</label>
|
||||
<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>
|
||||
<label class="form-group-label-override">{{'REPLICATION.REPLI_MODE' | translate}}</label>
|
||||
<div class="radio" style="display:inherit;">
|
||||
<input type="radio" id="push_base" name="replicationMode" [value]=true [(ngModel)]="isPushMode" [ngModelOptions]="{standalone: true}">
|
||||
<label for="push_base">Push-based</label>
|
||||
<input type="radio" id="pull_base" name="replicationMode" [value]=false [(ngModel)]="isPushMode" [ngModelOptions]="{standalone: true}">
|
||||
<label for="pull_base">Pull-based</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--source registry-->
|
||||
<div *ngIf="!isPushMode" class="form-group form-group-override">
|
||||
<label class="form-group-label-override required">{{'REPLICATION.SOURCE_REGISTRY' | translate}}</label>
|
||||
<div class="form-select">
|
||||
<div class="select endpointSelect pull-left">
|
||||
<select id="src_registry_id" (change)="sourceChange($event)" formControlName="src_registry_id">
|
||||
<option *ngFor="let source of sourceList" [ngValue]="source.id">{{source.name}}-{{source.url}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label *ngIf="noEndpointInfo.length != 0" class="colorRed alertLabel">{{noEndpointInfo | translate}}</label>
|
||||
<span class="alertLabel goLink" *ngIf="noEndpointInfo.length != 0" (click)="goRegistry()">{{'REPLICATION.ENDPOINTS' | translate}}</span>
|
||||
</div>
|
||||
<!--source namespace -->
|
||||
<div class="form-group form-group-override">
|
||||
<label class="form-group-label-override required">{{'REPLICATION.SOURCE_NAMESPACES' | translate}}</label>
|
||||
<div formArrayName="src_namespaces">
|
||||
<div class="width-315" *ngFor="let src_namespace of src_namespaces.controls; let i=index">
|
||||
<div class="select endpointSelect pull-left">
|
||||
<input type="text" [formControlName]="i" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" class="inputWidth" required maxlength="255">
|
||||
</div>
|
||||
<clr-icon shape="times-circle" class="is-solid" (click)="deleteNamespace(i)"></clr-icon>
|
||||
</div>
|
||||
</div>
|
||||
<clr-icon id="addSourceNamespace" shape="plus-circle" class="is-solid mr-t-15" (click)="addNewNamespace()"></clr-icon>
|
||||
</div>
|
||||
<!--images/Filter-->
|
||||
<div class="form-group form-group-override">
|
||||
<label class="form-group-label-override">{{'REPLICATION.SOURCE_IMAGES_FILTER' | translate}}</label>
|
||||
@ -58,46 +70,52 @@
|
||||
<div class="filterSelect" *ngFor="let filter of filters.controls; let i=index">
|
||||
<div [formGroupName]="i">
|
||||
<div class="select floatSetPar">
|
||||
<select formControlName="kind" #selectedValue (change)="filterChange($event, selectedValue.value)" id="{{i}}"
|
||||
name="{{filterListData[i]?.name}}">
|
||||
<select formControlName="kind" #selectedValue (change)="filterChange($event, selectedValue.value)" id="{{i}}" name="{{filterListData[i]?.name}}">
|
||||
<option *ngFor="let opt of filterListData[i]?.options;" value="{{opt}}">{{opt}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
|
||||
[class.invalid]='(filter.value.dirty || filter.value.touched) && filter.value.invalid'>
|
||||
<label aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='(filter.value.dirty || filter.value.touched) && filter.value.invalid'>
|
||||
<input type="text" #filterValue required size="14" formControlName="value" [attr.disabled]="(filterListData[i]?.name=='label') ?'' : null">
|
||||
<span class="tooltip-content">{{'TOOLTIP.EMPTY' | translate}}</span>
|
||||
</label>
|
||||
<div class="arrowSet" [hidden]="!(filterListData[i]?.name=='label')" (click)="openLabelList(selectedValue.value, i, $event)">
|
||||
<clr-icon shape="angle" ></clr-icon>
|
||||
<clr-icon shape="angle"></clr-icon>
|
||||
</div>
|
||||
<clr-icon shape="warning-standard" class="is-solid is-warning warning-icon" size="14" [hidden]="!deletedLabelCount || !(filterListData[i]?.name=='label')"></clr-icon>
|
||||
<clr-icon shape="times-circle" class="is-solid" (click)="deleteFilter(i)"></clr-icon>
|
||||
<div *ngIf="!withAdmiral">
|
||||
<hbr-filter-label [projectId]="projectId" [selectedLabelInfo]="filterLabelInfo" [isOpen]="filterListData[i].isOpen"
|
||||
(selectedLabels)="selectedLabelList($event, i)" (closePanelEvent)="filterListData[i].isOpen = false"></hbr-filter-label>
|
||||
<hbr-filter-label [projectId]="projectId" [selectedLabelInfo]="filterLabelInfo" [isOpen]="filterListData[i].isOpen" (selectedLabels)="selectedLabelList($event, i)"
|
||||
(closePanelEvent)="filterListData[i].isOpen = false"></hbr-filter-label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<clr-icon id="add-label-list" shape="plus-circle" class="is-solid plus-position" [hidden]="isFilterHide" (click)="addNewFilter()"></clr-icon>
|
||||
<clr-icon id="add-label-list" shape="plus-circle" class="is-solid mr-t-11" [hidden]="isFilterHide" (click)="addNewFilter()"></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" class="form-select">
|
||||
<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>
|
||||
<!--destination registry-->
|
||||
<div *ngIf="isPushMode" class="form-group form-group-override">
|
||||
|
||||
<label class="form-group-label-override required">{{'REPLICATION.DEST_REGISTRY' | translate}}</label>
|
||||
<div class="form-select">
|
||||
<div class="select endpointSelect pull-left">
|
||||
<select id="dest_registry_id" (change)="targetChange($event)" formControlName="dest_registry_id">
|
||||
<option *ngFor="let target of targetList" [ngValue]="target.id">{{target.name}}-{{target.url}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label *ngIf="noEndpointInfo.length != 0" class="colorRed alertLabel">{{noEndpointInfo | translate}}</label>
|
||||
<span class="alertLabel goLink" *ngIf="noEndpointInfo.length != 0" (click)="goRegistry()">{{'REPLICATION.ENDPOINTS'
|
||||
| translate}}</span>
|
||||
<span class="alertLabel goLink" *ngIf="noEndpointInfo.length != 0" (click)="goRegistry()">{{'REPLICATION.ENDPOINTS' | translate}}</span>
|
||||
</div>
|
||||
<!--destination namespaces -->
|
||||
<div class="form-group form-group-override">
|
||||
<label class="form-group-label-override">{{'REPLICATION.DEST_NAMESPACE' | translate}}</label>
|
||||
<div class="form-select">
|
||||
<div class="select endpointSelect pull-left">
|
||||
<input formControlName="dest_namespace" type="text" id="dest_namespace" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" class="inputWidth"
|
||||
maxlength="255">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Trigger-->
|
||||
@ -113,44 +131,24 @@
|
||||
</select>
|
||||
</div>
|
||||
<!--on push-->
|
||||
<div formGroupName="schedule_param" class="schedule-style">
|
||||
<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 formGroupName="schedule_param">
|
||||
<div [hidden]="!isScheduleOpt">
|
||||
<label>Cron String</label>
|
||||
<label for="targetCron" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right">
|
||||
<input type="text" name=targetCron id="targetCron" required class="form-control cron-input" formControlName="cron">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.CRON_REQUIRED' | translate }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<!--weekly-->
|
||||
<span [hidden]="!weeklySchedule || !isScheduleOpt">on </span>
|
||||
<div [hidden]="!weeklySchedule || !isScheduleOpt" class="select floatSet weekday-width">
|
||||
<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 [hidden]="!isImmediate" class="clr-form-control rule-width">
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox [checked]="false" id="ruleDeletion" formControlName="replicate_deletion" class="clr-checkbox">
|
||||
<input type="checkbox" clrCheckbox [checked]="false" id="ruleDeletion" formControlName="deletion" class="clr-checkbox">
|
||||
<label for="ruleDeletion" class="clr-control-label">{{'REPLICATION.DELETE_REMOTE_IMAGES' | translate}}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
<div class="clr-form-control rule-width">
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox [checked]="true" id="ruleExit" formControlName="replicate_existing_image_now"
|
||||
class="clr-checkbox">
|
||||
<label for="ruleExit" class="clr-control-label">{{'REPLICATION.REPLICATE_IMMEDIATE' | translate}}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading-center">
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
@ -159,9 +157,7 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<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>
|
||||
<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]="!isValid || !hasFormChange()">{{ 'BUTTON.SAVE' | translate }}</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -146,12 +146,19 @@ h4 {
|
||||
}
|
||||
|
||||
.form-group-override {
|
||||
padding-left: 170px !important;
|
||||
padding-left: 200px;
|
||||
}
|
||||
|
||||
.form-group>label:first-child {
|
||||
font-size: 14px;
|
||||
width: 6.5rem;
|
||||
.form-group {
|
||||
>label:first-child{
|
||||
font-size: 14px;
|
||||
width: 7rem;
|
||||
}
|
||||
.radio {
|
||||
label {
|
||||
margin-right:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-select {
|
||||
@ -199,7 +206,7 @@ clr-modal {
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.plus-position {
|
||||
.mr-t-11{
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
@ -214,4 +221,14 @@ clr-modal {
|
||||
.loading-center {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.width-315 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width:315px;
|
||||
}
|
||||
|
||||
.mr-t-15 {
|
||||
margin-top: 15px;
|
||||
}
|
@ -48,48 +48,14 @@ describe("CreateEditRuleComponent (inline template)", () => {
|
||||
id: 1,
|
||||
name: "sync_01",
|
||||
description: "",
|
||||
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
|
||||
}
|
||||
],
|
||||
src_registry_id: 2,
|
||||
src_namespaces: ["name1", "name2"],
|
||||
trigger: {
|
||||
kind: "Manual",
|
||||
schedule_param: null
|
||||
},
|
||||
filters: [],
|
||||
replicate_existing_image_now: false,
|
||||
replicate_deletion: false
|
||||
deletion: false
|
||||
}
|
||||
];
|
||||
let mockJobs: ReplicationJobItem[] = [
|
||||
@ -127,39 +93,55 @@ describe("CreateEditRuleComponent (inline template)", () => {
|
||||
let mockEndpoints: Endpoint[] = [
|
||||
{
|
||||
id: 1,
|
||||
endpoint: "https://10.117.4.151",
|
||||
name: "target_01",
|
||||
username: "admin",
|
||||
password: "",
|
||||
credential: {
|
||||
access_key: "admin",
|
||||
access_secret: "",
|
||||
type: "basic"
|
||||
},
|
||||
description: "test",
|
||||
insecure: false,
|
||||
type: 0
|
||||
name: "target_01",
|
||||
type: "Harbor",
|
||||
url: "https://10.117.4.151"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
endpoint: "https://10.117.5.142",
|
||||
name: "target_02",
|
||||
username: "AAA",
|
||||
password: "",
|
||||
credential: {
|
||||
access_key: "AAA",
|
||||
access_secret: "",
|
||||
type: "basic"
|
||||
},
|
||||
description: "test",
|
||||
insecure: false,
|
||||
type: 0
|
||||
name: "target_02",
|
||||
type: "Harbor",
|
||||
url: "https://10.117.5.142"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
endpoint: "https://101.1.11.111",
|
||||
name: "target_03",
|
||||
username: "admin",
|
||||
password: "",
|
||||
credential: {
|
||||
access_key: "admin",
|
||||
access_secret: "",
|
||||
type: "basic"
|
||||
},
|
||||
description: "test",
|
||||
insecure: false,
|
||||
type: 0
|
||||
name: "target_03",
|
||||
type: "Harbor",
|
||||
url: "https://101.1.11.111"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
endpoint: "http://4.4.4.4",
|
||||
name: "target_04",
|
||||
username: "",
|
||||
password: "",
|
||||
credential: {
|
||||
access_key: "admin",
|
||||
access_secret: "",
|
||||
type: "basic"
|
||||
},
|
||||
description: "test",
|
||||
insecure: true,
|
||||
type: 0
|
||||
name: "target_04",
|
||||
type: "Harbor",
|
||||
url: "https://4.4.4.4"
|
||||
}
|
||||
];
|
||||
|
||||
@ -167,48 +149,14 @@ describe("CreateEditRuleComponent (inline template)", () => {
|
||||
id: 1,
|
||||
name: "sync_01",
|
||||
description: "",
|
||||
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
|
||||
}
|
||||
],
|
||||
src_namespaces: ["namespace1", "namespace2"],
|
||||
src_registry_id: 10,
|
||||
trigger: {
|
||||
kind: "Manual",
|
||||
schedule_param: null
|
||||
},
|
||||
filters: [],
|
||||
replicate_existing_image_now: false,
|
||||
replicate_deletion: false
|
||||
deletion: false
|
||||
};
|
||||
|
||||
let fixture: ComponentFixture<ReplicationComponent>;
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
import { Filter, ReplicationRule, Endpoint, Label } from "../service/interface";
|
||||
import { Subject , Subscription } from "rxjs";
|
||||
import {debounceTime, distinctUntilChanged} from "rxjs/operators";
|
||||
import { FormArray, FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { FormArray, FormBuilder, FormGroup, Validators, FormControl } from "@angular/forms";
|
||||
import { clone, compareValue, isEmptyObject, toPromise } from "../utils";
|
||||
import { InlineAlertComponent } from "../inline-alert/inline-alert.component";
|
||||
import { ReplicationService } from "../service/replication.service";
|
||||
@ -45,6 +45,7 @@ const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
|
||||
})
|
||||
export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
_localTime: Date = new Date();
|
||||
sourceList: Endpoint[] = [];
|
||||
targetList: Endpoint[] = [];
|
||||
projectList: Project[] = [];
|
||||
selectedProjectList: Project[] = [];
|
||||
@ -54,22 +55,13 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
isImmediate = false;
|
||||
noProjectInfo = "";
|
||||
noEndpointInfo = "";
|
||||
isPushMode = true;
|
||||
noSelectedProject = true;
|
||||
noSelectedEndpoint = true;
|
||||
filterCount = 0;
|
||||
alertClosed = false;
|
||||
triggerNames: string[] = ["Manual", "Immediate", "Scheduled"];
|
||||
scheduleNames: string[] = ["Daily", "Weekly"];
|
||||
weekly: string[] = [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday"
|
||||
];
|
||||
filterSelect: string[] = ["repository", "tag", "label"];
|
||||
filterSelect: string[] = ["type", "repository", "tag", "label"];
|
||||
ruleNameTooltip = "REPLICATION.NAME_TOOLTIP";
|
||||
headerTitle = "REPLICATION.ADD_POLICY";
|
||||
|
||||
@ -87,10 +79,16 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
deletedLabelCount = 0;
|
||||
deletedLabelInfo: string;
|
||||
|
||||
namespaceList: [any] = [{
|
||||
id: 1,
|
||||
name: "namespace1"
|
||||
}];
|
||||
|
||||
confirmSub: Subscription;
|
||||
ruleForm: FormGroup;
|
||||
formArrayLabel: FormArray;
|
||||
copyUpdateForm: ReplicationRule;
|
||||
cronString: string;
|
||||
|
||||
@Input() projectId: number;
|
||||
@Input() projectName: string;
|
||||
@ -138,23 +136,16 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.withAdmiral) {
|
||||
this.filterSelect = ["repository", "tag"];
|
||||
this.filterSelect = ["type", "repository", "tag"];
|
||||
}
|
||||
|
||||
toPromise<Endpoint[]>(this.endpointService.getEndpoints())
|
||||
.then(targets => {
|
||||
this.targetList = targets || [];
|
||||
.then(endPoints => {
|
||||
this.targetList = endPoints || [];
|
||||
this.sourceList = endPoints || [];
|
||||
})
|
||||
.catch((error: any) => this.errorHandler.error(error));
|
||||
|
||||
if (!this.projectId) {
|
||||
toPromise<Project[]>(this.proService.listProjects("", undefined))
|
||||
.then(targets => {
|
||||
this.projectList = targets || [];
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
this.nameChecker
|
||||
.pipe(debounceTime(300))
|
||||
.pipe(distinctUntilChanged())
|
||||
@ -182,39 +173,19 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.proNameChecker
|
||||
.pipe(debounceTime(500))
|
||||
.pipe(distinctUntilChanged())
|
||||
.subscribe((resp: string) => {
|
||||
let name = this.ruleForm.controls["projects"].value[0].name;
|
||||
this.noProjectInfo = "";
|
||||
this.selectedProjectList = [];
|
||||
toPromise<Project[]>(this.proService.listProjects(name, undefined))
|
||||
.then((res: any) => {
|
||||
if (res) {
|
||||
this.selectedProjectList = res.slice(0, 10);
|
||||
// if input value exit in project list
|
||||
let pro = res.find((data: any) => data.name === name);
|
||||
if (!pro) {
|
||||
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
|
||||
this.noSelectedProject = true;
|
||||
} else {
|
||||
this.noProjectInfo = "";
|
||||
this.noSelectedProject = false;
|
||||
this.setProject([pro]);
|
||||
}
|
||||
} else {
|
||||
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
|
||||
this.noSelectedProject = true;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
this.errorHandler.error(error);
|
||||
this.noProjectInfo = "REPLICATION.NO_PROJECT_INFO";
|
||||
this.noSelectedProject = true;
|
||||
});
|
||||
});
|
||||
sourceChange($event): void {
|
||||
if ($event && $event.target) {
|
||||
if ($event.target["value"] === "-1") {
|
||||
this.noSelectedEndpoint = true;
|
||||
return;
|
||||
}
|
||||
let selecedTarget: Endpoint = this.sourceList.find(
|
||||
source => source.id === +$event.target["value"]
|
||||
);
|
||||
this.noSelectedEndpoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -228,11 +199,11 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.proNameChecker.unsubscribe();
|
||||
}
|
||||
}
|
||||
get src_namespaces(): FormArray { return this.ruleForm.get('src_namespaces') as FormArray; }
|
||||
|
||||
get isValid() {
|
||||
return !(
|
||||
!this.isRuleNameValid ||
|
||||
this.noSelectedProject ||
|
||||
this.noSelectedEndpoint ||
|
||||
this.inProgress
|
||||
);
|
||||
@ -240,23 +211,21 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
|
||||
createForm() {
|
||||
this.formArrayLabel = this.fb.array([]);
|
||||
|
||||
this.ruleForm = this.fb.group({
|
||||
name: ["", Validators.required],
|
||||
description: "",
|
||||
projects: this.fb.array([]),
|
||||
targets: this.fb.array([]),
|
||||
src_registry_id: new FormControl(),
|
||||
src_namespaces: new FormArray([new FormControl('')], Validators.required),
|
||||
dest_registry_id: new FormControl(),
|
||||
dest_namespace: "",
|
||||
trigger: this.fb.group({
|
||||
kind: this.triggerNames[0],
|
||||
schedule_param: this.fb.group({
|
||||
type: this.scheduleNames[0],
|
||||
weekday: 1,
|
||||
offtime: "08:00"
|
||||
cron: ""
|
||||
})
|
||||
}),
|
||||
filters: this.fb.array([]),
|
||||
replicate_existing_image_now: true,
|
||||
replicate_deletion: false
|
||||
deletion: false
|
||||
});
|
||||
}
|
||||
|
||||
@ -267,33 +236,27 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
trigger: {
|
||||
kind: this.triggerNames[0],
|
||||
schedule_param: {
|
||||
type: this.scheduleNames[0],
|
||||
weekday: 1,
|
||||
offtime: "08:00"
|
||||
cron: ""
|
||||
}
|
||||
},
|
||||
replicate_existing_image_now: true,
|
||||
replicate_deletion: false
|
||||
deletion: false
|
||||
});
|
||||
this.setProject([this.emptyProject]);
|
||||
this.setTarget([this.emptyEndpoint]);
|
||||
this.setFilter([]);
|
||||
|
||||
this.copyUpdateForm = clone(this.ruleForm.value);
|
||||
}
|
||||
|
||||
updateForm(rule: ReplicationRule): void {
|
||||
rule.trigger = this.updateTrigger(rule.trigger);
|
||||
this.ruleForm.reset({
|
||||
name: rule.name,
|
||||
description: rule.description,
|
||||
src_namespaces: rule.src_namespaces,
|
||||
dest_namespace: rule.dest_namespace,
|
||||
trigger: rule.trigger,
|
||||
replicate_existing_image_now: rule.replicate_existing_image_now,
|
||||
replicate_deletion: rule.replicate_deletion
|
||||
deletion: rule.deletion
|
||||
});
|
||||
this.setProject(rule.projects);
|
||||
|
||||
this.noSelectedProject = false;
|
||||
this.setTarget(rule.targets);
|
||||
this.noSelectedEndpoint = false;
|
||||
|
||||
if (rule.filters) {
|
||||
@ -315,7 +278,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
|
||||
let delLabel = '';
|
||||
filterLabels.forEach((data: any) => {
|
||||
if (data.kind === this.filterSelect[2]) {
|
||||
if (data.kind === this.filterSelect[3]) {
|
||||
if (!data.value.deleted) {
|
||||
count++;
|
||||
this.filterLabelInfo.push(data.value);
|
||||
@ -337,7 +300,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
if (delLabel || count) {
|
||||
let len = filterLabels.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let lab = filterLabels.find(data => data.kind === this.filterSelect[2]);
|
||||
let lab = filterLabels.find(data => data.kind === this.filterSelect[3]);
|
||||
if (lab) { filterLabels.splice(filterLabels.indexOf(lab), 1); }
|
||||
}
|
||||
filterLabels.push({ kind: 'label', value: count + ' labels' });
|
||||
@ -364,19 +327,12 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.ruleForm.setControl("filters", filterFormArray);
|
||||
}
|
||||
|
||||
get targets(): FormArray {
|
||||
return this.ruleForm.get("targets") as FormArray;
|
||||
}
|
||||
setTarget(targets: Endpoint[]) {
|
||||
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,
|
||||
value: ['', Validators.required]
|
||||
value: ''
|
||||
});
|
||||
}
|
||||
|
||||
@ -398,7 +354,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// if before select, $event is label
|
||||
if (!this.withAdmiral && name === this.filterSelect[2] && data.name === value) {
|
||||
if (!this.withAdmiral && name === this.filterSelect[3] && data.name === value) {
|
||||
this.labelInputVal = controlArray.controls[index].get('value').value.split(' ')[0];
|
||||
data.isOpen = false;
|
||||
controlArray.controls[index].get('value').setValue('');
|
||||
@ -421,7 +377,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
|
||||
// when input value is label, then open label panel
|
||||
openLabelList(labelTag: string, indexId: number, $event: any) {
|
||||
if (!this.withAdmiral && labelTag === this.filterSelect[2]) {
|
||||
if (!this.withAdmiral && labelTag === this.filterSelect[3]) {
|
||||
this.filterListData.forEach((data, index) => {
|
||||
if (index === indexId) {
|
||||
data.isOpen = true;
|
||||
@ -438,10 +394,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.noSelectedEndpoint = true;
|
||||
return;
|
||||
}
|
||||
let selecedTarget: Endpoint = this.targetList.find(
|
||||
target => target.id === +$event.target["value"]
|
||||
);
|
||||
this.setTarget([selecedTarget]);
|
||||
this.noSelectedEndpoint = false;
|
||||
}
|
||||
}
|
||||
@ -486,6 +438,14 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
addNewNamespace(): void {
|
||||
this.src_namespaces.push(new FormControl());
|
||||
}
|
||||
|
||||
deleteNamespace(index: number): void {
|
||||
this.src_namespaces.removeAt(index);
|
||||
}
|
||||
|
||||
addNewFilter(): void {
|
||||
const controlArray = <FormArray>this.ruleForm.get('filters');
|
||||
if (this.filterCount === 0) {
|
||||
@ -513,7 +473,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
if (this.filterCount >= this.filterSelect.length) {
|
||||
this.isFilterHide = true;
|
||||
}
|
||||
if (controlArray.controls[this.filterCount - 1].get('kind').value === this.filterSelect[2] && this.labelInputVal) {
|
||||
if (controlArray.controls[this.filterCount - 1].get('kind').value === this.filterSelect[3] && this.labelInputVal) {
|
||||
controlArray.controls[this.filterCount - 1].get('value').setValue(this.labelInputVal + ' labels');
|
||||
}
|
||||
|
||||
@ -565,23 +525,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
|
||||
// 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 {
|
||||
@ -634,54 +577,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (trigger["kind"] === this.triggerNames[1]) {
|
||||
this.isImmediate = true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
setFilterLabelVal(filters: any[]) {
|
||||
let labels: any = filters.find(data => data.kind === this.filterSelect[2]);
|
||||
|
||||
@ -703,7 +598,12 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
// add new Replication rule
|
||||
this.inProgress = true;
|
||||
let copyRuleForm: ReplicationRule = this.ruleForm.value;
|
||||
copyRuleForm.trigger = this.setTriggerVaule(copyRuleForm.trigger);
|
||||
copyRuleForm.trigger = null;
|
||||
if (this.isPushMode) {
|
||||
copyRuleForm.src_registry_id = null;
|
||||
} else {
|
||||
copyRuleForm.dest_registry_id = null;
|
||||
}
|
||||
// rewrite key name of label when filer contain labels.
|
||||
if (copyRuleForm.filters) { this.setFilterLabelVal(copyRuleForm.filters); }
|
||||
|
||||
@ -787,12 +687,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
} else {
|
||||
this.headerTitle = "REPLICATION.ADD_POLICY";
|
||||
if (this.projectId) {
|
||||
this.setProject([
|
||||
{ project_id: this.projectId, name: this.projectName }
|
||||
]);
|
||||
this.noSelectedProject = false;
|
||||
}
|
||||
this.copyUpdateForm = clone(this.ruleForm.value);
|
||||
}
|
||||
}
|
||||
@ -820,71 +714,6 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
||||
this.goToRegistry.emit();
|
||||
}
|
||||
|
||||
// UTC time
|
||||
public getOfftime(daily_time: any): string {
|
||||
let timeOffset = 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;
|
||||
}
|
||||
|
||||
hasChanges(): boolean {
|
||||
let formValue = clone(this.ruleForm.value);
|
||||
let initValue = clone(this.copyUpdateForm);
|
||||
|
@ -24,51 +24,51 @@ describe("EndpointComponent (inline template)", () => {
|
||||
let mockData: Endpoint[] = [
|
||||
{
|
||||
id: 1,
|
||||
endpoint: "https://10.117.4.151",
|
||||
url: "https://10.117.4.151",
|
||||
name: "target_01",
|
||||
username: "admin",
|
||||
password: "",
|
||||
insecure: true,
|
||||
type: 0
|
||||
type: "Harbor"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
endpoint: "https://10.117.5.142",
|
||||
url: "https://10.117.5.142",
|
||||
name: "target_02",
|
||||
username: "AAA",
|
||||
password: "",
|
||||
insecure: false,
|
||||
type: 0
|
||||
type: "Harbor"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
endpoint: "https://101.1.11.111",
|
||||
url: "https://101.1.11.111",
|
||||
name: "target_03",
|
||||
username: "admin",
|
||||
password: "",
|
||||
insecure: false,
|
||||
type: 0
|
||||
type: "Harbor"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
endpoint: "http://4.4.4.4",
|
||||
url: "http://4.4.4.4",
|
||||
name: "target_04",
|
||||
username: "",
|
||||
password: "",
|
||||
insecure: false,
|
||||
type: 0
|
||||
type: "Harbor"
|
||||
}
|
||||
];
|
||||
|
||||
let mockOne: Endpoint[] = [
|
||||
{
|
||||
id: 1,
|
||||
endpoint: "https://10.117.4.151",
|
||||
url: "https://10.117.4.151",
|
||||
name: "target_01",
|
||||
username: "admin",
|
||||
password: "",
|
||||
insecure: false,
|
||||
type: 0
|
||||
type: "Harbor"
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -77,12 +77,12 @@ export class EndpointComponent implements OnInit, OnDestroy {
|
||||
|
||||
get initEndpoint(): Endpoint {
|
||||
return {
|
||||
endpoint: "",
|
||||
url: "",
|
||||
name: "",
|
||||
username: "",
|
||||
password: "",
|
||||
insecure: false,
|
||||
type: 0
|
||||
type: ""
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -20,83 +20,25 @@ describe('ListReplicationRuleComponent (inline template)', () => {
|
||||
let mockRules: ReplicationRule[] = [
|
||||
{
|
||||
"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": [{
|
||||
"endpoint": "",
|
||||
"id": 0,
|
||||
"insecure": false,
|
||||
"name": "khans3",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"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,
|
||||
"deletion": false,
|
||||
"src_namespaces": ["name1", "name2"],
|
||||
"src_registry_id": 3
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"deletion": false,
|
||||
"src_namespaces": ["name1", "name2"],
|
||||
"dest_registry_id": 3
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -29,83 +29,25 @@ describe('Replication Component (inline template)', () => {
|
||||
let mockRules: ReplicationRule[] = [
|
||||
{
|
||||
"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,
|
||||
"deletion": false,
|
||||
"src_registry_id": 3,
|
||||
"src_namespaces": ["name1"]
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"deletion": false,
|
||||
"dest_registry_id": 5,
|
||||
"src_namespaces": ["name1"]
|
||||
}
|
||||
];
|
||||
|
||||
@ -142,47 +84,24 @@ describe('Replication Component (inline template)', () => {
|
||||
let mockEndpoints: Endpoint[] = [
|
||||
{
|
||||
"id": 1,
|
||||
"endpoint": "https://10.117.4.151",
|
||||
"url": "https://10.117.4.151",
|
||||
"name": "target_01",
|
||||
"username": "admin",
|
||||
"password": "",
|
||||
"insecure": false,
|
||||
"type": 0
|
||||
"type": "Harbor"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"endpoint": "https://10.117.5.142",
|
||||
"url": "https://10.117.5.142",
|
||||
"name": "target_02",
|
||||
"username": "AAA",
|
||||
"password": "",
|
||||
"insecure": false,
|
||||
"type": 0
|
||||
"type": "Harbor"
|
||||
},
|
||||
];
|
||||
|
||||
// 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
|
||||
|
@ -75,12 +75,12 @@ export interface Tag extends Base {
|
||||
* extends {Base}
|
||||
*/
|
||||
export interface Endpoint extends Base {
|
||||
endpoint: string;
|
||||
url: string;
|
||||
name: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
insecure: boolean;
|
||||
type: number;
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@ -97,24 +97,13 @@ export interface ReplicationRule extends Base {
|
||||
id?: number;
|
||||
name: string;
|
||||
description: string;
|
||||
projects: Project[];
|
||||
targets: Endpoint[];
|
||||
trigger: Trigger;
|
||||
filters: Filter[];
|
||||
replicate_existing_image_now?: boolean;
|
||||
replicate_deletion?: boolean;
|
||||
// id?: number;
|
||||
// name: string;
|
||||
// description: string;
|
||||
// src_registry_id: number;
|
||||
// src_namespaces: [];
|
||||
// dest_registry_id: number;
|
||||
// dest_namespace: string;
|
||||
// trigger: Trigger;
|
||||
// filter: Filter[];
|
||||
// deletion: boolean;
|
||||
// override: boolean;
|
||||
// enabled: boolean;
|
||||
deletion?: boolean;
|
||||
src_registry_id?: number;
|
||||
dest_registry_id?: number;
|
||||
src_namespaces: string [];
|
||||
dest_namespace?: string;
|
||||
}
|
||||
|
||||
export class Filter {
|
||||
|
@ -216,7 +216,8 @@ export class ReplicationDefaultService extends ReplicationService {
|
||||
rule != null &&
|
||||
rule.name !== undefined &&
|
||||
rule.name.trim() !== "" &&
|
||||
rule.targets.length !== 0
|
||||
rule.src_namespaces && rule.src_namespaces.length > 0 &&
|
||||
(!!rule.dest_registry_id || !! rule.src_registry_id)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -447,7 +447,12 @@
|
||||
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
|
||||
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
|
||||
"ACKNOWLEDGE": "Acknowledge",
|
||||
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it."
|
||||
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it.",
|
||||
"REPLI_MODE": "Replication mode",
|
||||
"SOURCE_REGISTRY":"Source registry",
|
||||
"SOURCE_NAMESPACES":"Source namespaces",
|
||||
"DEST_REGISTRY":"Destination registry",
|
||||
"DEST_NAMESPACE":"Destination namespace"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "New Endpoint",
|
||||
|
@ -448,7 +448,12 @@
|
||||
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
|
||||
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
|
||||
"ACKNOWLEDGE": "Acknowledge",
|
||||
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it."
|
||||
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it.",
|
||||
"REPLI_MODE": "Replication mode",
|
||||
"SOURCE_REGISTRY":"Source registry",
|
||||
"SOURCE_NAMESPACES":"Source namespaces",
|
||||
"DEST_REGISTRY":"Destination registry",
|
||||
"DEST_NAMESPACE":"Destination namespace"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "Nuevo Endpoint",
|
||||
|
@ -429,7 +429,12 @@
|
||||
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
|
||||
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
|
||||
"ACKNOWLEDGE": "Acknowledge",
|
||||
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it."
|
||||
"RULE_DISABLED": "This rule has been disabled because a label used in its filter has been deleted. \n Edit the rule and update its filter to enable it.",
|
||||
"REPLI_MODE": "Replication mode",
|
||||
"SOURCE_REGISTRY":"Source registry",
|
||||
"SOURCE_NAMESPACES":"Source namespaces",
|
||||
"DEST_REGISTRY":"Destination registry",
|
||||
"DEST_NAMESPACE":"Destination namespace"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "Nouveau Point Final",
|
||||
|
@ -447,7 +447,12 @@
|
||||
"NAME_TOOLTIP": "nome da regra de replicação deve conter ao menos 2 caracteres sendo caracteres minusculos, números e ._- e devem iniciar com letras e números.",
|
||||
"DELETED_LABEL_INFO": "Removidas as label(s) '{{param}}' referenciadas no filtro, clique 'SALVAR' para atualizar o filtro e habilitar essa regra.",
|
||||
"ACKNOWLEDGE": "Reconhecer",
|
||||
"RULE_DISABLED": "Essa regra foi desabilitada pois uma label usada no seu filtro foi removida. \n Edite a regra e atualize seu filtro para habilitá-la."
|
||||
"RULE_DISABLED": "Essa regra foi desabilitada pois uma label usada no seu filtro foi removida. \n Edite a regra e atualize seu filtro para habilitá-la.",
|
||||
"REPLI_MODE": "Replication mode",
|
||||
"SOURCE_REGISTRY":"Source registry",
|
||||
"SOURCE_NAMESPACES":"Source namespaces",
|
||||
"DEST_REGISTRY":"Destination registry",
|
||||
"DEST_NAMESPACE":"Destination namespace"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "Novo Endpoint",
|
||||
|
@ -448,7 +448,12 @@
|
||||
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
|
||||
"DELETED_LABEL_INFO": "过滤项有被删除的标签 {{param}} , 点击保存按钮更新过滤项使规则可用。",
|
||||
"ACKNOWLEDGE": "确认",
|
||||
"RULE_DISABLED": "这个规则因为过滤选项中的标签被删除已经不能用了,更新过滤项以便重新启用规则。"
|
||||
"RULE_DISABLED": "这个规则因为过滤选项中的标签被删除已经不能用了,更新过滤项以便重新启用规则。",
|
||||
"REPLI_MODE": "复制模式",
|
||||
"SOURCE_REGISTRY":"源Registry",
|
||||
"SOURCE_NAMESPACES":"源Namespace",
|
||||
"DEST_REGISTRY":"目的Registry",
|
||||
"DEST_NAMESPACE":"目的Namespace"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "新建目标",
|
||||
|
Loading…
Reference in New Issue
Block a user