harbor/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component....

825 lines
40 KiB
HTML

<clr-modal
[(clrModalOpen)]="createEditRuleOpened"
[clrModalStaticBackdrop]="true"
[clrModalClosable]="false">
<h3 class="modal-title">{{ headerTitle | translate }}</h3>
<div class="modal-body modal-body-height">
<inline-alert (confirmEvt)="confirmCancel($event)"></inline-alert>
<form
[formGroup]="ruleForm"
novalidate
class="clr-form clr-form-horizontal">
<div
class="clr-form-control"
[class.clr-error]="
(ruleForm.controls.name.touched &&
ruleForm.controls.name.invalid) ||
!isRuleNameValid
">
<label class="clr-control-label required">{{
'REPLICATION.NAME' | translate
}}</label>
<div class="clr-control-container">
<div class="clr-input-wrapper">
<input
class="clr-input width-input"
type="text"
id="ruleName"
size="35"
pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$"
required
maxlength="255"
formControlName="name"
#ruleName
(keyup)="checkRuleName()"
autocomplete="off" />
<clr-icon
class="clr-validate-icon"
shape="exclamation-circle"></clr-icon>
<span
class="spinner spinner-inline spinner-pos"
[hidden]="!inNameChecking"></span>
</div>
<clr-control-error
*ngIf="
(ruleForm.controls.name.touched &&
ruleForm.controls.name.invalid) ||
!isRuleNameValid
"
>{{ ruleNameTooltip | translate }}</clr-control-error
>
</div>
</div>
<!--Description-->
<clr-textarea-container>
<label>{{ 'REPLICATION.DESCRIPTION' | translate }}</label>
<textarea
clrTextarea
type="text"
id="ruleDescription"
class="width-input"
row="3"
formControlName="description"></textarea>
</clr-textarea-container>
<!-- replication mode -->
<clr-radio-container clrInline>
<label>{{ 'REPLICATION.REPLI_MODE' | translate }}</label>
<clr-radio-wrapper>
<input
clrRadio
type="radio"
id="push_base"
name="replicationMode"
[value]="true"
[disabled]="policyId >= 0 || onGoing"
[(ngModel)]="isPushMode"
(change)="pushModeChange()"
[ngModelOptions]="{ standalone: true }" />
<label for="push_base"
>Push-based
<clr-tooltip class="mode-tooltip">
<clr-icon
clrTooltipTrigger
shape="info-circle"
size="24"></clr-icon>
<clr-tooltip-content
clrPosition="top-left"
clrSize="md"
*clrIfOpen>
<span>{{
'TOOLTIP.PUSH_BASED' | translate
}}</span>
</clr-tooltip-content>
</clr-tooltip>
</label>
</clr-radio-wrapper>
<clr-radio-wrapper>
<input
clrRadio
type="radio"
id="pull_base"
name="replicationMode"
[value]="false"
[disabled]="policyId >= 0 || onGoing"
[(ngModel)]="isPushMode"
(change)="pullModeChange()"
[ngModelOptions]="{ standalone: true }" />
<label for="pull_base"
>Pull-based
<clr-tooltip class="mode-tooltip">
<clr-icon
clrTooltipTrigger
shape="info-circle"
size="24"></clr-icon>
<clr-tooltip-content
clrPosition="top-left"
clrSize="md"
*clrIfOpen>
<span>{{
'TOOLTIP.PULL_BASED' | translate
}}</span>
</clr-tooltip-content>
</clr-tooltip>
</label>
</clr-radio-wrapper>
</clr-radio-container>
<!--source registry-->
<div class="clr-form-control" *ngIf="!isPushMode">
<label class="required clr-control-label">{{
'REPLICATION.SOURCE_REGISTRY' | translate
}}</label>
<div class="clr-control-container">
<div class="clr-select-wrapper">
<select
class="clr-select width-input"
id="src_registry_id"
(change)="sourceChange($event)"
formControlName="src_registry"
[compareWith]="equals">
<option class="display-none"></option>
<option
*ngFor="let source of sourceList"
[ngValue]="source">
{{ source.name }}-{{ source.url }}
</option>
</select>
</div>
<div class="space-between">
<span
*ngIf="noEndpointInfo.length !== 0"
class="alert-label"
>{{ noEndpointInfo | translate }}</span
>
<span
class="alert-label go-link"
*ngIf="noEndpointInfo.length !== 0"
(click)="goRegistry()"
>{{ 'REPLICATION.ENDPOINTS' | translate }}</span
>
</div>
</div>
</div>
<!--images/Filter-->
<div class="clr-form-control">
<label class="clr-control-label">{{
'REPLICATION.SOURCE_RESOURCE_FILTER' | translate
}}</label>
<span
class="spinner spinner-inline spinner-position"
[hidden]="onGoing === false"></span>
<div formArrayName="filters" class="clr-control-container">
<div
class="filterSelect"
*ngFor="let filter of filters.controls; let i = index">
<div [formGroupName]="i" class="flex">
<label class="sub-label"
>{{
'REPLICATION.' +
supportedFilters[i]?.type.toUpperCase()
| translate
}}:</label
>
<div
*ngIf="supportedFilters[i]?.style === 'input'"
class="flex">
<div
class="clr-select-wrapper mr-1"
*ngIf="supportedFilters[i]?.type === 'tag'">
<select
class="clr-select width-match-exclude"
formControlName="decoration">
<option value="matches">
{{
'TAG_RETENTION.MAT' | translate
}}
</option>
<option value="excludes">
{{
'TAG_RETENTION.EXC' | translate
}}
</option>
</select>
</div>
<div class="clr-input-wrapper">
<input
class="clr-input"
autocomplete="off"
[ngClass]="{
'width-name-resource':
supportedFilters[i]?.type !==
'tag',
'width-tag-label':
supportedFilters[i]?.type ===
'tag'
}"
(input)="trimText($event)"
type="text"
#filterValue
size="14"
formControlName="value"
id="{{
'filter_' +
supportedFilters[i]?.type
}}" />
</div>
</div>
<div
class="select resource-box clr-select-wrapper"
*ngIf="
supportedFilters[i]?.style === 'radio' &&
supportedFilters[i]?.values.length > 1
">
<select
class="clr-select width-name-resource"
formControlName="value"
#selectedValue
id="{{
'select_' + supportedFilters[i]?.type
}}"
name="{{ supportedFilters[i]?.type }}">
<option value="">
{{ 'REPLICATION.ALL' | translate }}
</option>
<option
*ngFor="
let value of supportedFilters[i]
?.values
"
value="{{ value }}">
{{ value }}
</option>
</select>
</div>
<div
class="select flex"
*ngIf="
supportedFilters[i]?.type === 'label' &&
supportedFilters[i]?.style === 'list'
">
<div class="clr-select-wrapper mr-1">
<select
class="clr-select width-match-exclude"
formControlName="decoration">
<option value="matches">
{{
'TAG_RETENTION.MAT' | translate
}}
</option>
<option value="excludes">
{{
'TAG_RETENTION.EXC' | translate
}}
</option>
</select>
</div>
<div
class="clr-input-wrapper position-relative width-tag-label">
<input
class="clr-input width-tag-label label-input"
autocomplete="off"
type="text"
id="labelFilter"
[(ngModel)]="stringForLabelFilter"
[ngModelOptions]="{
standalone: true
}" />
<clr-dropdown>
<clr-icon
class="down"
clrDropdownTrigger
shape="caret"
dir="down">
</clr-icon>
<clr-dropdown-menu
[ngStyle]="{ 'max-height.px': 230 }"
class="right-align"
clrPosition="bottom-left"
*clrIfOpen>
<button
type="button"
class="dropdown-item flex"
*ngFor="
let value of supportedFilterLabels
"
(click)="
stickLabel(value.name)
">
<clr-icon
shape="check"
[style.visibility]="
isSelect(value.name)
? 'visible'
: 'hidden'
"></clr-icon>
<hbr-label-piece
[label]="value"
[labelWidth]="
130
"></hbr-label-piece>
</button>
<button
type="button"
class="dropdown-item space-between no-labels"
*ngIf="
!supportedFilterLabels?.length
">
<span class="alert-label">{{
'REPLICATION.NO_LABEL_INFO'
| translate
}}</span>
<span
class="alert-label go-link"
routerLink="/harbor/labels"
>{{
'CONFIG.LABEL'
| translate
}}</span
>
</button>
</clr-dropdown-menu>
</clr-dropdown>
</div>
</div>
<div
class="resource-box"
*ngIf="
supportedFilters[i]?.style === 'radio' &&
supportedFilters[i]?.values.length <= 1
">
<span>{{ supportedFilters[i]?.values }}</span>
</div>
<clr-tooltip>
<clr-icon
clrTooltipTrigger
shape="info-circle"
size="24"></clr-icon>
<clr-tooltip-content
clrPosition="top-left"
clrSize="md"
*clrIfOpen>
<span
class="tooltip-content"
*ngIf="
supportedFilters[i]?.type === 'name'
"
>{{
'TOOLTIP.NAME_FILTER' | translate
}}</span
>
<span
class="tooltip-content"
*ngIf="
supportedFilters[i]?.type === 'tag'
"
>{{
'TOOLTIP.TAG_FILTER' | translate
}}</span
>
<span
class="tooltip-content"
*ngIf="
supportedFilters[i]?.type ===
'label'
"
>{{
'TOOLTIP.LABEL_FILTER' | translate
}}</span
>
<span
class="tooltip-content"
*ngIf="
supportedFilters[i]?.type ===
'resource'
"
>{{
'TOOLTIP.RESOURCE_FILTER'
| translate
}}</span
>
</clr-tooltip-content>
</clr-tooltip>
</div>
</div>
</div>
</div>
<!--destination registry-->
<div *ngIf="isPushMode" class="clr-form-control">
<label class="clr-control-label required">{{
'REPLICATION.DEST_REGISTRY' | translate
}}</label>
<div class="form-select clr-control-container">
<div class="clr-select-wrapper">
<select
class="clr-select width-input"
id="dest_registry"
(change)="targetChange($event)"
formControlName="dest_registry"
[compareWith]="equals">
<option class="display-none"></option>
<option
*ngFor="let target of targetList"
[ngValue]="target">
{{ target.name }}-{{ target.url }}
</option>
</select>
</div>
<div class="space-between">
<label
*ngIf="noEndpointInfo.length !== 0"
class="alert-label"
>{{ noEndpointInfo | translate }}</label
>
<span
class="alert-label go-link"
*ngIf="noEndpointInfo.length !== 0"
(click)="goRegistry()"
>{{ 'REPLICATION.ENDPOINTS' | translate }}</span
>
</div>
</div>
</div>
<!--destination namespaces -->
<div class="clr-form-control">
<label for="dest_namespace" class="clr-control-label">{{
'REPLICATION.DESTINATION' | translate
}}</label>
<div
class="clr-control-container"
[class.clr-error]="
(ruleForm.controls.dest_namespace.dirty ||
ruleForm.controls.dest_namespace.touched) &&
ruleForm.controls.dest_namespace.invalid
">
<div class="clr-input-wrapper flex">
<label class="sub-label"
>{{ 'REPLICATION.NAMESPACE' | translate }}:</label
>
<input
autocomplete="off"
class="clr-input width-name-resource"
formControlName="dest_namespace"
type="text"
id="dest_namespace"
pattern="^[a-z0-9]+(?:[/._-][a-z0-9]+)*$"
maxlength="255" />
<clr-tooltip class="des-tooltip">
<clr-icon
clrTooltipTrigger
shape="info-circle"
size="24"></clr-icon>
<clr-tooltip-content
clrPosition="top-left"
clrSize="lg"
*clrIfOpen>
<span>{{
'TOOLTIP.DESTINATION_NAMESPACE' | translate
}}</span>
</clr-tooltip-content>
</clr-tooltip>
</div>
<clr-control-error
class="margin-left-90px"
*ngIf="
(ruleForm.controls.dest_namespace.dirty ||
ruleForm.controls.dest_namespace.touched) &&
ruleForm.controls.dest_namespace.invalid
">
{{ 'REPLICATION.NAMESPACE_TOOLTIP' | translate }}
</clr-control-error>
</div>
</div>
<!--destination namespaces -->
<div class="clr-form-control flattening">
<label
for="dest_namespace_replace_count"
class="clr-control-label"></label>
<div class="clr-control-container">
<div class="clr-select-wrapper flex">
<label class="sub-label"
>{{
'REPLICATION.REPO_FLATTENING' | translate
}}:</label
>
<select
[attr.disabled]="
ruleForm.controls.dest_namespace.invalid ||
!ruleForm.controls.dest_namespace.value
? 'disabled'
: null
"
id="dest_namespace_replace_count"
formControlName="dest_namespace_replace_count"
class="clr-select width-name-resource">
<option
*ngFor="let item of flattenLevelMap | keyvalue"
[value]="item.key">
{{ item.value | translate }}
</option>
</select>
<clr-tooltip class="des-tooltip">
<clr-icon
clrTooltipTrigger
shape="info-circle"
size="24"></clr-icon>
<clr-tooltip-content
class="flatten"
clrPosition="top-left"
clrSize="lg"
*clrIfOpen>
<div>
{{
'REPLICATION.FLATTEN_LEVEL_TIP'
| translate
}}
</div>
<div>
{{
'REPLICATION.FLATTEN_LEVEL_TIP_ALL'
| translate
}}
</div>
<div>
{{
'REPLICATION.FLATTEN_LEVEL_TIP_NO'
| translate
}}
</div>
<div>
{{
'REPLICATION.FLATTEN_LEVEL_TIP_1'
| translate
}}
</div>
<div>
{{
'REPLICATION.FLATTEN_LEVEL_TIP_2'
| translate
}}
</div>
<div>
{{
'REPLICATION.FLATTEN_LEVEL_TIP_3'
| translate
}}
</div>
</clr-tooltip-content>
</clr-tooltip>
</div>
</div>
</div>
<!--Trigger-->
<div class="clr-form-control">
<label class="clr-control-label required">{{
'REPLICATION.TRIGGER_MODE' | translate
}}</label>
<div class="clr-control-container">
<div formGroupName="trigger">
<!--on trigger-->
<div class="select clr-select-wrapper">
<select
(change)="changeTrigger($event)"
id="ruleTrigger"
formControlName="type"
class="clr-select width-input">
<option
*ngFor="let trigger of supportedTriggers"
[value]="trigger">
{{
'REPLICATION.' + trigger.toUpperCase()
| translate
}}
</option>
</select>
</div>
<div
formGroupName="trigger_settings"
class="clr-form-control">
<div class="flex" [hidden]="isNotSchedule()">
<label for="targetCron" class="required"
>Cron String</label
>
<div
class="clr-control-container"
[class.clr-error]="
cronInputShouldShowError()
">
<div class="clr-input-wrapper">
<input
autocomplete="off"
(input)="inputInvalid($event)"
type="text"
name="targetCron"
id="targetCron"
required
pattern="^0\s(?!(\*\s)).+$"
class="form-control cron-input clr-input"
formControlName="cron" />
</div>
<clr-control-error
*ngIf="cronInputShouldShowError()">
{{
'REPLICATION.CRON_ERROR_TIP'
| translate
}}
</clr-control-error>
</div>
<a
href="javascript:void(0)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-lg tooltip-top-left top-7 cron-tooltip">
<clr-icon
shape="info-circle"
class="info-tips-icon"
size="24"></clr-icon>
<div class="tooltip-content table-box">
<cron-tooltip></cron-tooltip>
</div>
</a>
</div>
</div>
</div>
<div
class="clr-checkbox-wrapper clr-form-control mt-0"
[hidden]="isNotEventBased()">
<input
type="checkbox"
class="clr-checkbox"
[checked]="false"
id="ruleDeletion"
formControlName="deletion" />
<label for="ruleDeletion">{{
'REPLICATION.DELETE_REMOTE_IMAGES' | translate
}}</label>
</div>
</div>
</div>
<!--speed-->
<div class="clr-form-control bandwidth">
<label for="speed" class="required clr-control-label"
>{{ 'REPLICATION.BANDWIDTH' | translate }}
</label>
<div
class="clr-control-container"
[class.clr-error]="
ruleForm.controls.speed.invalid &&
(ruleForm.controls.speed.dirty ||
ruleForm.controls.speed.touched)
">
<div class="clr-checkbox-wrapper speed">
<input
type="text"
id="speed"
name="speed"
class="clr-input"
formControlName="speed"
autocomplete="off"
required
pattern="(^-1$)|(^([1-9]+)([0-9]+)*$)"
maxlength="5" />
<clr-icon
class="clr-validate-icon"
shape="exclamation-circle"></clr-icon>
</div>
<div
class="clr-select-wrapper unit-select"
[class.unit]="
ruleForm.controls.speed.invalid &&
(ruleForm.controls.speed.dirty ||
ruleForm.controls.speed.touched)
">
<select
class="clr-select unit-select"
[ngModelOptions]="{ standalone: true }"
id="speed_unit"
name="speed_unit"
[(ngModel)]="selectedUnit">
<option
*ngFor="let unit of speedUnits"
[value]="unit.UNIT">
{{ unit.UNIT }}
</option>
</select>
<clr-tooltip class="ml-10px">
<clr-icon
clrTooltipTrigger
shape="info-circle"
size="24"></clr-icon>
<clr-tooltip-content
clrPosition="top-left"
clrSize="lg"
*clrIfOpen>
<span>{{
'REPLICATION.BANDWIDTH_TOOLTIP' | translate
}}</span>
</clr-tooltip-content>
</clr-tooltip>
</div>
<clr-control-error
*ngIf="
ruleForm.controls.speed.invalid &&
(ruleForm.controls.speed.dirty ||
ruleForm.controls.speed.touched)
"
class="tooltip-content">
{{ 'REPLICATION.BANDWIDTH_ERROR_TIP' | translate }}
</clr-control-error>
</div>
</div>
<!--override and enable and chunk-->
<div class="clr-form-control">
<label class="clr-control-label">{{
'SCANNER.OPTIONS' | translate
}}</label>
<div class="clr-control-container clr-control-inline">
<div class="clr-checkbox-wrapper">
<input
type="checkbox"
class="clr-checkbox"
[checked]="true"
id="overridePolicy"
formControlName="override" />
<label for="overridePolicy" class="clr-control-label"
>{{ 'REPLICATION.OVERRIDE_INFO' | translate }}
<clr-tooltip class="override-tooltip">
<clr-icon
clrTooltipTrigger
shape="info-circle"
size="24"></clr-icon>
<clr-tooltip-content
clrPosition="top-left"
clrSize="md"
*clrIfOpen>
<span>{{
'TOOLTIP.OVERRIDE' | translate
}}</span>
</clr-tooltip-content>
</clr-tooltip>
</label>
</div>
<div
class="clr-checkbox-wrapper"
[hidden]="!showChunkOption">
<input
type="checkbox"
id="by-chunk"
formControlName="copy_by_chunk"
class="clr-checkbox" />
<label for="by-chunk" class="clr-control-label"
>{{ 'REPLICATION.COPY_BY_CHUNK' | translate }}
<clr-tooltip class="override-tooltip">
<clr-icon
clrTooltipTrigger
shape="info-circle"
size="24"></clr-icon>
<clr-tooltip-content
clrPosition="top-left"
clrSize="md"
*clrIfOpen>
<span>{{
'REPLICATION.COPY_BY_CHUNK_TIP'
| translate
}}</span>
</clr-tooltip-content>
</clr-tooltip>
</label>
</div>
<div class="clr-checkbox-wrapper" [hidden]="policyId < 0">
<input
type="checkbox"
[checked]="true"
id="enablePolicy"
formControlName="enabled"
class="clr-checkbox" />
<label for="enablePolicy" class="clr-control-label">{{
'REPLICATION.ENABLED_RULE' | translate
}}</label>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button
type="button"
id="ruleBtnCancel"
class="btn btn-outline"
[disabled]="inProgress"
(click)="onCancel()">
{{ 'BUTTON.CANCEL' | translate }}
</button>
<button
type="submit"
id="ruleBtnOk"
class="btn btn-primary"
[clrLoading]="inProgress"
(click)="onSubmit()"
[disabled]="inProgress || !isValid || !hasFormChange()">
{{ 'BUTTON.SAVE' | translate }}
</button>
</div>
</clr-modal>