Add bandwidth input to adding replication rule (#15550)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
孙世军 2021-09-15 16:59:14 +08:00 committed by GitHub
parent 192a97c85f
commit c5003f38ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 167 additions and 28 deletions

View File

@ -18,3 +18,7 @@ export enum Decoration {
MATCHES = 'matches',
EXCLUDES = 'excludes'
}
export enum BandwidthUnit {
MB = 'Mbps',
KB = 'Kbps'
}

View File

@ -238,6 +238,40 @@
<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">
<label for="speed" class="required clr-control-label">{{'REPLICATION.BANDWIDTH' | translate}}
<clr-tooltip>
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
<span>{{'REPLICATION.BANDWIDTH_TOOLTIP' | translate}}</span>
</clr-tooltip-content>
</clr-tooltip>
</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" [class.unit]="(ruleForm.controls.speed.invalid && (ruleForm.controls.speed.dirty || ruleForm.controls.speed.touched))">
<select class="clr-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>
</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-->
<div class="clr-form-control">
<label class="clr-control-label"></label>
<div class="clr-control-container">
<div class="clr-checkbox-wrapper clr-form-control">
<input type="checkbox" class="clr-checkbox" [checked]="true" id="overridePolicy" formControlName="override">
<label for="overridePolicy">{{'REPLICATION.OVERRIDE_INFO' | translate}}
@ -259,6 +293,6 @@
</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]="!isValid || !hasFormChange()">{{ 'BUTTON.SAVE' | 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>

View File

@ -267,3 +267,13 @@ clr-modal {
.right-align {
transform: translateX(-4.3rem) translateY(1.25rem)!important;
}
.unit {
width: 4rem;
}
.speed {
width: 14.4rem;
display: inline-block;
input {
width: 12rem;
}
}

View File

@ -39,7 +39,8 @@ describe("CreateEditRuleComponent (inline template)", () => {
filters: [],
deletion: false,
enabled: true,
override: true
override: true,
speed: -1
}
];
let mockJobs: ReplicationJobItem[] = [
@ -152,7 +153,8 @@ describe("CreateEditRuleComponent (inline template)", () => {
filters: [],
deletion: false,
enabled: true,
override: true
override: true,
speed: -1
};
let mockRegistryInfo = {

View File

@ -20,7 +20,7 @@ import {
EventEmitter,
Output
} from "@angular/core";
import { Filter, ReplicationRule } from "../../../../../shared/services/interface";
import { Filter, ReplicationRule } from "../../../../../shared/services";
import { forkJoin, Observable, Subject, Subscription } from "rxjs";
import { debounceTime, distinctUntilChanged, finalize } from "rxjs/operators";
import { FormArray, FormBuilder, FormGroup, Validators, FormControl } from "@angular/forms";
@ -35,10 +35,11 @@ import { RegistryService } from "../../../../../../../ng-swagger-gen/services/re
import { Registry } from "../../../../../../../ng-swagger-gen/models/registry";
import { Label } from "../../../../../../../ng-swagger-gen/models/label";
import { LabelService } from "../../../../../../../ng-swagger-gen/services/label.service";
import { Decoration, Flatten_I18n_MAP, Flatten_Level } from "../../replication";
import { BandwidthUnit, Decoration, Flatten_I18n_MAP, Flatten_Level } from "../../replication";
const PREFIX: string = '0 ';
const PAGE_SIZE: number = 100;
export const KB_TO_MB: number = 1024;
@Component({
selector: "hbr-create-edit-rule",
@ -82,6 +83,16 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
@ViewChild(InlineAlertComponent, {static: true}) inlineAlert: InlineAlertComponent;
flattenLevelMap = Flatten_I18n_MAP;
speedUnits = [
{
UNIT: BandwidthUnit.KB,
},
{
UNIT: BandwidthUnit.MB,
}
];
selectedUnit: string = BandwidthUnit.KB;
copySpeedUnit: string = BandwidthUnit.KB;
constructor(
private fb: FormBuilder,
private repService: ReplicationService,
@ -207,10 +218,13 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
get isValid() {
if (this.ruleForm.controls["dest_namespace"].value) {
if (this.ruleForm.controls["dest_namespace"].invalid) {this.ruleForm.get('trigger').get('trigger_settings').get('cron').value
if (this.ruleForm.controls["dest_namespace"].invalid) {
return false;
}
}
if (this.ruleForm.controls["speed"].invalid) {
return false;
}
let controlName = !!this.ruleForm.controls["name"].value;
let sourceRegistry = !!this.ruleForm.controls["src_registry"].value;
let destRegistry = !!this.ruleForm.controls["dest_registry"].value;
@ -242,7 +256,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
filters: this.fb.array([]),
enabled: true,
deletion: false,
override: true
override: true,
speed: -1
});
}
@ -268,15 +283,19 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
deletion: false,
enabled: true,
override: true,
dest_namespace_replace_count: Flatten_Level.FLATTEN_LEVEl_1
dest_namespace_replace_count: Flatten_Level.FLATTEN_LEVEl_1,
speed: -1
});
this.isPushMode = true;
this.selectedUnit = BandwidthUnit.KB;
}
updateRuleFormAndCopyUpdateForm(rule: ReplicationRule): void {
this.isPushMode = rule.dest_registry.id !== 0;
setTimeout(() => {
// convert speed unit to KB or MB
let speed: number = this.convertToInputValue(rule.speed);
// There is no trigger_setting type when the harbor is upgraded from the old version.
rule.trigger.trigger_settings = rule.trigger.trigger_settings ? rule.trigger.trigger_settings : {cron: ''};
this.ruleForm.reset({
@ -289,12 +308,14 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
trigger: rule.trigger,
deletion: rule.deletion,
enabled: rule.enabled,
override: rule.override
override: rule.override,
speed: speed
});
let filtersArray = this.getFilterArray(rule);
this.noSelectedEndpoint = false;
this.setFilter(filtersArray);
this.copyUpdateForm = clone(this.ruleForm.value);
this.copySpeedUnit = this.selectedUnit;
// keep trigger same value
this.copyUpdateForm.trigger = clone(rule.trigger);
this.copyUpdateForm.filters = this.copyUpdateForm.filters === null ? [] : this.copyUpdateForm.filters;
@ -373,6 +394,9 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
public hasFormChange(): boolean {
if (this.copySpeedUnit !== this.selectedUnit) {// speed unit has been changed
return true;
}
return !isEmptyObject(this.hasChanges());
}
@ -383,6 +407,8 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
// add new Replication rule
this.inProgress = true;
let copyRuleForm: ReplicationRule = this.ruleForm.value;
// need to convert unit to KB for speed property
copyRuleForm.speed = this.convertToKB(copyRuleForm.speed);
copyRuleForm.dest_namespace_replace_count = copyRuleForm.dest_namespace_replace_count
? parseInt(copyRuleForm.dest_namespace_replace_count.toString(), 10) : 0;
if (this.isPushMode) {
@ -660,4 +686,24 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
});
}
// convert 'MB' or 'KB' to 'KB'
convertToKB(inputValue: number): number {
if (!inputValue) { // return default value
return -1;
}
if (this.selectedUnit === BandwidthUnit.KB) {
return +inputValue;
}
return inputValue * KB_TO_MB;
}
// convert 'KB' to 'MB' or 'KB'
convertToInputValue(realSpeed: number): number {
if (realSpeed >= KB_TO_MB && realSpeed % KB_TO_MB === 0) {
this.selectedUnit = BandwidthUnit.MB;
return Math.ceil(realSpeed / KB_TO_MB);
} else {
this.selectedUnit = BandwidthUnit.KB;
return realSpeed ? realSpeed : -1;
}
}
}

View File

@ -48,6 +48,7 @@
<clr-dg-column class="min-width">{{'REPLICATION.DESTINATION_NAMESPACE' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPLICATION.DES_REPO_FLATTENING' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'trigger'">{{'REPLICATION.REPLICATION_TRIGGER' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'speed'">{{'REPLICATION.BANDWIDTH' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'description'">{{'REPLICATION.DESCRIPTION' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPLICATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let p of rules; let i=index" [clrDgItem]="p">
@ -82,6 +83,7 @@
</clr-dg-cell>
<clr-dg-cell>{{(getFlattenLevelString(p.dest_namespace_replace_count)) | translate}}</clr-dg-cell>
<clr-dg-cell>{{p.trigger ? p.trigger.type : ''}}</clr-dg-cell>
<clr-dg-cell>{{getBandwidthStr(p.speed) | translate}}</clr-dg-cell>
<clr-dg-cell>
{{p.description ? trancatedDescription(p.description) : '-'}}
<clr-tooltip>

View File

@ -7,7 +7,6 @@ import { ErrorHandler } from '../../../../../shared/units/error-handler';
import { ReplicationService } from '../../../../../shared/services';
import { OperationService } from "../../../../../shared/components/operation/operation.service";
import { of } from 'rxjs';
import { CURRENT_BASE_HREF } from "../../../../../shared/units/utils";
import { delay } from "rxjs/operators";
import {HttpHeaders, HttpResponse} from "@angular/common/http";
import { SharedTestingModule } from "../../../../../shared/shared.module";
@ -26,7 +25,8 @@ describe('ListReplicationRuleComponent (inline template)', () => {
"src_namespaces": ["name1", "name2"],
"src_registry": {id: 3},
"enabled": true,
"override": true
"override": true,
"speed": -1
},
{
"id": 2,
@ -39,7 +39,8 @@ describe('ListReplicationRuleComponent (inline template)', () => {
"src_namespaces": ["name1", "name2"],
"dest_registry": {id: 3},
"enabled": true,
"override": true
"override": true,
"speed": -1
},
];

View File

@ -40,7 +40,8 @@ import { errorHandler } from "../../../../../shared/units/shared.utils";
import { ConfirmationAcknowledgement } from "../../../../global-confirmation-dialog/confirmation-state-message";
import { ConfirmationMessage } from "../../../../global-confirmation-dialog/confirmation-message";
import { HELM_HUB } from "../../../../../shared/services/endpoint.service";
import { Flatten_I18n_MAP } from "../../replication";
import { BandwidthUnit, Flatten_I18n_MAP } from "../../replication";
import { KB_TO_MB } from "../create-edit-rule/create-edit-rule.component";
@Component({
selector: "hbr-list-replication-rule",
templateUrl: "./list-replication-rule.component.html",
@ -268,4 +269,13 @@ export class ListReplicationRuleComponent {
}
return level;
}
getBandwidthStr(speed: number): string {
if (speed >= KB_TO_MB) {
return '' + (speed / KB_TO_MB).toFixed(2) + BandwidthUnit.MB;
}
if (speed > 0 && speed < KB_TO_MB) {
return '' + speed + BandwidthUnit.KB;
}
return 'REPLICATION.UNLIMITED';
}
}

View File

@ -1,17 +1,13 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { ReplicationExecution } from "../../../../../../../ng-swagger-gen/models/replication-execution";
import { ReplicationTasksComponent } from "./replication-tasks.component";
import { ActivatedRoute } from "@angular/router";
import { ReplicationService } from "../../../../../../../ng-swagger-gen/services";
import { ErrorHandler } from "../../../../../shared/units/error-handler";
import { RouterTestingModule } from "@angular/router/testing";
import { TranslateModule } from "@ngx-translate/core";
import { of, Subscription } from "rxjs";
import { delay } from "rxjs/operators";
import { HttpHeaders, HttpResponse } from "@angular/common/http";
import { ClarityModule } from "@clr/angular";
import { ReplicationTask } from "../../../../../../../ng-swagger-gen/models/replication-task";
import { SharedTestingModule } from "../../../../../shared/shared.module";

View File

@ -12,7 +12,6 @@ import { ReplicationJobItem } from '../../../../shared/services';
import { OperationService } from "../../../../shared/components/operation/operation.service";
import { RouterTestingModule } from '@angular/router/testing';
import { of, Subscription } from 'rxjs';
import { CURRENT_BASE_HREF } from "../../../../shared/units/utils";
import { HttpHeaders, HttpResponse } from "@angular/common/http";
import { delay } from "rxjs/operators";
import { SharedTestingModule } from "../../../../shared/shared.module";
@ -32,7 +31,8 @@ describe('Replication Component (inline template)', () => {
"src_registry": {id: 3},
"src_namespaces": ["name1"],
"enabled": true,
"override": true
"override": true,
"speed": -1
},
{
"id": 2,
@ -45,7 +45,8 @@ describe('Replication Component (inline template)', () => {
"dest_registry": {id: 5},
"src_namespaces": ["name1"],
"enabled": true,
"override": true
"override": true,
"speed": -1
}
];

View File

@ -76,6 +76,7 @@ export interface ReplicationRule extends Base {
dest_namespace_replace_count?: number;
enabled: boolean;
override: boolean;
speed: number;
}
export class Filter {

View File

@ -649,7 +649,11 @@
"FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'",
"FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'",
"FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'",
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'"
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'",
"BANDWIDTH": "Bandwidth",
"BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0",
"BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1",
"UNLIMITED": "Unlimited"
},
"DESTINATION": {
"NEW_ENDPOINT": "Neuer Endpunkt",

View File

@ -649,7 +649,11 @@
"FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'",
"FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'",
"FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'",
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'"
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'",
"BANDWIDTH": "Bandwidth",
"BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0",
"BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1",
"UNLIMITED": "Unlimited"
},
"DESTINATION": {
"NEW_ENDPOINT": "New Endpoint",

View File

@ -651,7 +651,11 @@
"FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'",
"FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'",
"FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'",
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'"
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'",
"BANDWIDTH": "Bandwidth",
"BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0",
"BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1",
"UNLIMITED": "Unlimited"
},
"DESTINATION": {
"NEW_ENDPOINT": "Nuevo Endpoint",

View File

@ -639,7 +639,11 @@
"FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'",
"FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'",
"FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'",
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'"
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'",
"BANDWIDTH": "Bandwidth",
"BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0",
"BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1",
"UNLIMITED": "Unlimited"
},
"DESTINATION": {
"NEW_ENDPOINT": "Nouveau Point Final",

View File

@ -649,7 +649,11 @@
"FLATTEN_LEVEL_TIP_1": "'Nivelar 1 posição' (padrão): 'a/b/c/d/img' -> 'ns/b/c/d/img'",
"FLATTEN_LEVEL_TIP_2": "'Nivelar 2 posições': 'a/b/c/d/img' -> 'ns/c/d/img'",
"FLATTEN_LEVEL_TIP_3": "'Nivelar 3 posições': 'a/b/c/d/img' -> 'ns/d/img'",
"NOTE": "Nota: Chartmuseum suporta helm charts apenas com 2 níveis: 'a/chart'"
"NOTE": "Nota: Chartmuseum suporta helm charts apenas com 2 níveis: 'a/chart'",
"BANDWIDTH": "Bandwidth",
"BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0",
"BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1",
"UNLIMITED": "Unlimited"
},
"DESTINATION": {
"NEW_ENDPOINT": "Novo Endereço",

View File

@ -649,7 +649,11 @@
"FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'",
"FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'",
"FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'",
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'"
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'",
"BANDWIDTH": "Bandwidth",
"BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0",
"BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1",
"UNLIMITED": "Unlimited"
},
"DESTINATION": {
"NEW_ENDPOINT": "Yeni Uç Nokta",

View File

@ -651,7 +651,11 @@
"FLATTEN_LEVEL_TIP_1": "'替换1级'(默认项): 'a/b/c/d/img' -> 'ns/b/c/d/img'",
"FLATTEN_LEVEL_TIP_2": "'替换2级': 'a/b/c/d/img' -> 'ns/c/d/img'",
"FLATTEN_LEVEL_TIP_3": "'替换3级': 'a/b/c/d/img' -> 'ns/d/img'",
"NOTE": "注意: Chartmuseum Charts 仅支持2层的仓库结构如 'a/chart'"
"NOTE": "注意: Chartmuseum Charts 仅支持2层的仓库结构如 'a/chart'",
"BANDWIDTH": "带宽",
"BANDWIDTH_ERROR_TIP": "请输入-1或者大于0的整数",
"BANDWIDTH_TOOLTIP": "设置执行该条同步规则时的最大网络带宽。实际总带宽需要考虑并发执行的情况。如无需限制,请输入-1",
"UNLIMITED": "无限制"
},
"DESTINATION": {
"NEW_ENDPOINT": "新建目标",

View File

@ -646,7 +646,11 @@
"FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'",
"FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'",
"FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'",
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'"
"NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'",
"BANDWIDTH": "Bandwidth",
"BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0",
"BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1",
"UNLIMITED": "Unlimited"
},
"DESTINATION":{
"NEW_ENDPOINT": "新建目標",