mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-01 20:41:22 +01:00
Add immutable tag in project detail
Signed-off-by: Yogi_Wang <yawang@vmware.com>
This commit is contained in:
parent
5c5e475da4
commit
bfe19711db
3
.gitignore
vendored
3
.gitignore
vendored
@ -33,7 +33,7 @@ src/portal/typings/
|
||||
|
||||
src/portal/src/**/*.js
|
||||
src/portal/src/**/*.js.map
|
||||
|
||||
src/portal/lib/coverage
|
||||
**/npm*.log
|
||||
|
||||
**/*ngsummary.json
|
||||
@ -42,3 +42,4 @@ src/portal/src/**/*.js.map
|
||||
**/dist
|
||||
**/.bin
|
||||
src/core/conf/app.conf
|
||||
|
||||
|
@ -61,6 +61,7 @@ import { OidcOnboardComponent } from './oidc-onboard/oidc-onboard.component';
|
||||
import { LicenseComponent } from './license/license.component';
|
||||
import { SummaryComponent } from './project/summary/summary.component';
|
||||
import { TagRetentionComponent } from './project/tag-retention/tag-retention.component';
|
||||
import { ImmutableTagComponent } from './project/immutable-tag/immutable-tag.component';
|
||||
import { USERSTATICPERMISSION } from '@harbor/ui';
|
||||
import { ScannerComponent } from "./project/scanner/scanner.component";
|
||||
|
||||
@ -277,6 +278,16 @@ const harborRoutes: Routes = [
|
||||
},
|
||||
component: TagRetentionComponent
|
||||
},
|
||||
{
|
||||
path: 'immutable-tag',
|
||||
data: {
|
||||
permissionParam: {
|
||||
resource: USERSTATICPERMISSION.TAG_RETENTION.KEY,
|
||||
action: USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ
|
||||
}
|
||||
},
|
||||
component: ImmutableTagComponent
|
||||
},
|
||||
{
|
||||
path: 'webhook',
|
||||
data: {
|
||||
|
@ -0,0 +1,67 @@
|
||||
<clr-modal [(clrModalOpen)]="addRuleOpened" [clrModalStaticBackdrop]="true" [clrModalClosable]="true" [clrModalSize]="'lg'">
|
||||
<h3 class="modal-title" *ngIf="isAdd">{{'TAG_RETENTION.ADD_TITLE' | translate}}</h3>
|
||||
<h3 class="modal-title" *ngIf="!isAdd">{{'TAG_RETENTION.EDIT_TITLE' | translate}}</h3>
|
||||
<div class="modal-body no-scrolling">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<p class="color-97">{{'TAG_RETENTION.ADD_SUBTITLE' | translate}}</p>
|
||||
<div class="height-72">
|
||||
<div class="clr-row mt-1">
|
||||
<div class="clr-col-4">
|
||||
<span>{{'TAG_RETENTION.IN_REPOSITORIES' | translate}}</span>
|
||||
</div>
|
||||
<div class="clr-col-3">
|
||||
<div class="clr-select-wrapper w-100">
|
||||
<select id="scope_selectors" [(ngModel)]="repoSelect" class="clr-select w-100">
|
||||
<option *ngFor="let d of metadata?.scope_selectors[0]?.decorations"
|
||||
value="{{d}}">{{getI18nKey(d)|translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-col-5">
|
||||
<div class="w-100">
|
||||
<input required id="scope-input" [(ngModel)]="repositories" class="clr-input w-100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4"></div>
|
||||
<div class="clr-col-8">
|
||||
<span>{{'TAG_RETENTION.REP_SEPARATOR' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="height-72">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4">
|
||||
<label>{{'TAG_RETENTION.TAGS' | translate}}</label>
|
||||
</div>
|
||||
<div class="clr-col-3">
|
||||
<div class="clr-select-wrapper w-100">
|
||||
<select id="tag_selectors" [(ngModel)]="tagsSelect" class="clr-select w-100">
|
||||
<option *ngFor="let d of metadata?.tag_selectors[0]?.decorations"
|
||||
value="{{d}}">{{getI18nKey(d)|translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-col-5">
|
||||
<div class="w-100">
|
||||
<input required id="tag-input" [(ngModel)]="tagsInput" class="clr-input w-100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4"></div>
|
||||
<div class="clr-col-8">
|
||||
<span>{{'TAG_RETENTION.TAG_SEPARATOR' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" id="close-btn" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button [disabled]="canNotAdd()" type="button" id="add-edit-btn" class="btn btn-primary" (click)="add()">
|
||||
<span *ngIf="isAdd">{{'BUTTON.ADD' | translate}}</span>
|
||||
<span *ngIf="!isAdd">{{'BUTTON.SAVE' | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,7 @@
|
||||
.color-97 {
|
||||
color: #979797;
|
||||
}
|
||||
|
||||
.height-72 {
|
||||
height: 72px;
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { AddRuleComponent } from './add-rule.component';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ImmutableTagService } from '../immutable-tag.service';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component";
|
||||
import { ImmutableRetentionRule } from "../../tag-retention/retention";
|
||||
import { compareValue } from "@harbor/ui";
|
||||
describe('AddRuleComponent', () => {
|
||||
let component: AddRuleComponent;
|
||||
let fixture: ComponentFixture<AddRuleComponent>;
|
||||
let mockRule = {
|
||||
"id": 1,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "**"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AddRuleComponent, InlineAlertComponent],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
imports: [
|
||||
ClarityModule,
|
||||
FormsModule,
|
||||
NoopAnimationsModule,
|
||||
HttpClientTestingModule,
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
ImmutableTagService
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddRuleComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
component.addRuleOpened = true;
|
||||
component.repoSelect = mockRule.scope_selectors.repository[0].decoration;
|
||||
component.repositories = mockRule.scope_selectors.repository[0].pattern.replace(/[{}]/g, "");
|
||||
component.tagsSelect = mockRule.tag_selectors[0].decoration;
|
||||
component.tagsInput = mockRule.tag_selectors[0].pattern.replace(/[{}]/g, "");
|
||||
component.clickAdd = new EventEmitter<ImmutableRetentionRule>();
|
||||
component.rules = [];
|
||||
component.isAdd = true;
|
||||
component.open();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it("should rightly display default repositories and tag", async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let elRep: HTMLInputElement = fixture.nativeElement.querySelector("#scope-input");
|
||||
expect(elRep).toBeTruthy();
|
||||
expect(elRep.value.trim()).toEqual("**");
|
||||
let elTag: HTMLInputElement = fixture.nativeElement.querySelector("#tag-input");
|
||||
expect(elTag).toBeTruthy();
|
||||
expect(elTag.value.trim()).toEqual("**");
|
||||
});
|
||||
}));
|
||||
it("should rightly close", async(() => {
|
||||
fixture.detectChanges();
|
||||
let elRep: HTMLButtonElement = fixture.nativeElement.querySelector("#close-btn");
|
||||
elRep.dispatchEvent(new Event('click'));
|
||||
elRep.click();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.addRuleOpened).toEqual(false);
|
||||
});
|
||||
}));
|
||||
it("should be validating repeat rule ", async(() => {
|
||||
fixture.detectChanges();
|
||||
component.rules = [mockRule];
|
||||
const elRep: HTMLButtonElement = fixture.nativeElement.querySelector("#add-edit-btn");
|
||||
elRep.dispatchEvent(new Event('click'));
|
||||
elRep.click();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const elRep1: HTMLSpanElement = fixture.nativeElement.querySelector(".alert-text");
|
||||
expect(elRep1).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
@ -0,0 +1,177 @@
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Output,
|
||||
EventEmitter, ViewChild, Input,
|
||||
} from "@angular/core";
|
||||
import { ImmutableRetentionRule, RuleMetadate } from "../../tag-retention/retention";
|
||||
import { compareValue } from "@harbor/ui";
|
||||
import { ImmutableTagService } from "../immutable-tag.service";
|
||||
import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component";
|
||||
|
||||
const EXISTING_RULE = "TAG_RETENTION.EXISTING_RULE";
|
||||
const INVALID_RULE = "TAG_RETENTION.INVALID_RULE";
|
||||
@Component({
|
||||
selector: 'app-add-rule',
|
||||
templateUrl: './add-rule.component.html',
|
||||
styleUrls: ['./add-rule.component.scss']
|
||||
})
|
||||
export class AddRuleComponent implements OnInit, OnDestroy {
|
||||
addRuleOpened: boolean = false;
|
||||
@Output() clickAdd = new EventEmitter<ImmutableRetentionRule>();
|
||||
@Input() rules: ImmutableRetentionRule[];
|
||||
@Input() projectId: number;
|
||||
metadata: RuleMetadate = new RuleMetadate();
|
||||
rule: ImmutableRetentionRule = new ImmutableRetentionRule(this.projectId);
|
||||
isAdd: boolean = true;
|
||||
editRuleOrigin: ImmutableRetentionRule;
|
||||
onGoing: boolean = false;
|
||||
@ViewChild(InlineAlertComponent, { static: false }) inlineAlert: InlineAlertComponent;
|
||||
constructor(private immutableTagService: ImmutableTagService) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
}
|
||||
|
||||
get repoSelect() {
|
||||
if (this.rule && this.rule.scope_selectors && this.rule.scope_selectors.repository[0]) {
|
||||
return this.rule.scope_selectors.repository[0].decoration;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
set repoSelect(repoSelect) {
|
||||
if (this.rule && this.rule.scope_selectors && this.rule.scope_selectors.repository[0]) {
|
||||
this.rule.scope_selectors.repository[0].decoration = repoSelect;
|
||||
}
|
||||
}
|
||||
|
||||
set repositories(repositories) {
|
||||
if (this.rule && this.rule.scope_selectors && this.rule.scope_selectors.repository
|
||||
&& this.rule.scope_selectors.repository[0] && this.rule.scope_selectors.repository[0].pattern) {
|
||||
if (repositories.indexOf(",") !== -1) {
|
||||
this.rule.scope_selectors.repository[0].pattern = "{" + repositories + "}";
|
||||
} else {
|
||||
this.rule.scope_selectors.repository[0].pattern = repositories;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get repositories() {
|
||||
if (this.rule && this.rule.scope_selectors && this.rule.scope_selectors.repository
|
||||
&& this.rule.scope_selectors.repository[0] && this.rule.scope_selectors.repository[0].pattern) {
|
||||
return this.rule.scope_selectors.repository[0].pattern.replace(/[{}]/g, "");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
get tagsSelect() {
|
||||
if (this.rule && this.rule.tag_selectors && this.rule.tag_selectors[0]) {
|
||||
return this.rule.tag_selectors[0].decoration;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
set tagsSelect(tagsSelect) {
|
||||
if (this.rule && this.rule.tag_selectors && this.rule.tag_selectors[0]) {
|
||||
this.rule.tag_selectors[0].decoration = tagsSelect;
|
||||
}
|
||||
}
|
||||
|
||||
set tagsInput(tagsInput) {
|
||||
if (this.rule && this.rule.tag_selectors && this.rule.tag_selectors[0] && this.rule.tag_selectors[0].pattern) {
|
||||
|
||||
if (tagsInput.indexOf(",") !== -1) {
|
||||
this.rule.tag_selectors[0].pattern = "{" + tagsInput + "}";
|
||||
} else {
|
||||
this.rule.tag_selectors[0].pattern = tagsInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get tagsInput() {
|
||||
if (this.rule && this.rule.tag_selectors && this.rule.tag_selectors[0] && this.rule.tag_selectors[0].pattern) {
|
||||
return this.rule.tag_selectors[0].pattern.replace(/[{}]/g, "");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
canNotAdd(): boolean {
|
||||
if (this.onGoing) {
|
||||
return true;
|
||||
}
|
||||
if (!this.isAdd && compareValue(this.editRuleOrigin, this.rule)) {
|
||||
return true;
|
||||
}
|
||||
return !(
|
||||
this.rule && this.rule.scope_selectors && this.rule.scope_selectors.repository
|
||||
&& this.rule.scope_selectors.repository[0] && this.rule.scope_selectors.repository[0].pattern
|
||||
&& this.rule.scope_selectors.repository[0].pattern.replace(/[{}]/g, "")
|
||||
&& this.rule.tag_selectors && this.rule.tag_selectors[0] && this.rule.tag_selectors[0].pattern
|
||||
&& this.rule.tag_selectors[0].pattern.replace(/[{}]/g, ""));
|
||||
}
|
||||
|
||||
open() {
|
||||
this.addRuleOpened = true;
|
||||
this.inlineAlert.alertClose = true;
|
||||
this.onGoing = false;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.addRuleOpened = false;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
add() {
|
||||
// remove whitespaces
|
||||
this.rule.scope_selectors.repository[0].pattern = this.rule.scope_selectors.repository[0].pattern.replace(/\s+/g, "");
|
||||
this.rule.tag_selectors[0].pattern = this.rule.tag_selectors[0].pattern.replace(/\s+/g, "");
|
||||
if (this.rule.scope_selectors.repository[0].decoration !== "repoMatches"
|
||||
&& this.rule.scope_selectors.repository[0].pattern.indexOf("**") !== -1) {
|
||||
this.inlineAlert.showInlineError(INVALID_RULE);
|
||||
return;
|
||||
}
|
||||
if (this.isExistingRule()) {
|
||||
this.inlineAlert.showInlineError(EXISTING_RULE);
|
||||
return;
|
||||
}
|
||||
this.clickAdd.emit(this.rule);
|
||||
}
|
||||
isExistingRule(): boolean {
|
||||
if (this.rules && this.rules.length > 0) {
|
||||
for (let i = 0; i < this.rules.length; i++) {
|
||||
if (this.isSameRule(this.rules[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
isSameRule(rule: ImmutableRetentionRule): boolean {
|
||||
if (this.rule.scope_selectors.repository[0].decoration !== rule.scope_selectors.repository[0].decoration) {
|
||||
return false;
|
||||
}
|
||||
if (this.rule.scope_selectors.repository[0].pattern !== rule.scope_selectors.repository[0].pattern) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.rule.tag_selectors[0].decoration !== rule.tag_selectors[0].decoration) {
|
||||
return false;
|
||||
}
|
||||
return this.rule.tag_selectors[0].pattern === rule.tag_selectors[0].pattern;
|
||||
}
|
||||
|
||||
getI18nKey(str: string) {
|
||||
return this.immutableTagService.getI18nKey(str);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
<div class="clr-row pt-1 fw8">
|
||||
<div class="clr-col">
|
||||
<label class="label-left font-size-54">{{'TAG_RETENTION.RETENTION_RULES' | translate}}</label><span class="badge badge-3 ml-5">{{rules?.length ? rules?.length : 0}}/15</span>
|
||||
<span *ngIf="loadingRule" class="spinner spinner-inline ml-2">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row pt-1">
|
||||
<div class="clr-col">
|
||||
<ul *ngIf="rules?.length > 0" class="list-unstyled">
|
||||
<li class="rule" *ngFor="let rule of rules;let i = index;">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-2 flex-150">
|
||||
<div class="dropdown" [ngClass]="{open:ruleIndex===i}">
|
||||
<button (click)="openEditor(i)" id="{{'action'+i}}" class="dropdown-toggle btn btn-link btn-sm">
|
||||
{{'TAG_RETENTION.ACTION' | translate}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<button *ngIf="!rule?.disabled" type="button" id="{{'disable-btn'+i}}" class="dropdown-item" (click)="toggleDisable(rule,true)">{{'TAG_RETENTION.DISABLE' | translate}}</button>
|
||||
<button *ngIf="rule?.disabled" type="button" class="dropdown-item" (click)="toggleDisable(rule,false)">{{'TAG_RETENTION.ENABLE' | translate}}</button>
|
||||
<button type="button" id="{{'edit-btn'+i}}" class="dropdown-item" (click)="editRuleByIndex(i)">{{'TAG_RETENTION.EDIT' | translate}}</button>
|
||||
<button type="button" class="dropdown-item" id="{{'delete-btn'+i}}" (click)="deleteRule(rule.id)">{{'TAG_RETENTION.DELETE' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-col">
|
||||
<span>
|
||||
<clr-icon *ngIf="!rule?.disabled" class="color-green" shape="success-standard"></clr-icon>
|
||||
<clr-icon id="{{'disable-icon'+i}}" *ngIf="rule?.disabled" class="color-red" shape="error-standard"></clr-icon>
|
||||
</span>
|
||||
<span class="rule-name ml-5">
|
||||
<span>{{'TAG_RETENTION.IN_REPOSITORIES' | translate}}</span>
|
||||
<span>{{getI18nKey(rule?.scope_selectors?.repository[0]?.decoration)|translate}}</span>
|
||||
<span>{{formatPattern(rule?.scope_selectors?.repository[0]?.pattern)}}</span>
|
||||
<span>,</span>
|
||||
<span>{{'TAG_RETENTION.LOWER_TAGS' | translate}}</span>
|
||||
<span>{{getI18nKey(rule?.tag_selectors[0]?.decoration)|translate}}</span>
|
||||
<span id="{{'tag-selectors-patten'+i}}">{{formatPattern(rule?.tag_selectors[0]?.pattern)}}</span>
|
||||
<ng-container *ngIf="rule?.tag_selectors[1]?.pattern && rule?.tag_selectors[1]?.pattern">
|
||||
<span class="color-97">{{'TAG_RETENTION.AND' | translate}}</span>
|
||||
<span>{{'TAG_RETENTION.LOWER_LABELS' | translate}}</span>
|
||||
<span>{{getI18nKey(rule?.tag_selectors[1]?.decoration)|translate}}</span>
|
||||
<span>{{rule?.tag_selectors[1]?.pattern}}</span>
|
||||
</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="v-center clr-row" [ngClass]="{'pt-1':rules?.length > 0}">
|
||||
<div class="clr-col-2 flex-150"></div>
|
||||
<div class="clr-col-2">
|
||||
<button [disabled]="rules?.length >= 15" id="add-rule" class="btn btn-primary btn-sm" (click)="openAddRule()">{{'TAG_RETENTION.ADD_RULE' | translate}}</button>
|
||||
</div>
|
||||
<div class="clr-col-8 color-97 font-size-54">
|
||||
{{'TAG_RETENTION.ADD_RULE_HELP_1' | translate}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-add-rule #addRule [rules]="rules" [projectId]="projectId" (clickAdd)="clickAdd($event)"></app-add-rule>
|
||||
<div class="backdrop-transparent" (click)="ruleIndex = -1" *ngIf="ruleIndex !== -1"></div>
|
@ -0,0 +1,399 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
||||
|
||||
import { ImmutableTagComponent } from './immutable-tag.component';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { AddRuleComponent } from './add-rule/add-rule.component';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ImmutableTagService } from './immutable-tag.service';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { ErrorHandler, DefaultErrorHandler, clone } from '@harbor/ui';
|
||||
|
||||
describe('ImmutableTagComponent', () => {
|
||||
let component: ImmutableTagComponent;
|
||||
let addRuleComponent: AddRuleComponent;
|
||||
let immutableTagService: ImmutableTagService;
|
||||
let errorHandler: ErrorHandler;
|
||||
let fixture: ComponentFixture<ImmutableTagComponent>;
|
||||
let fixtureAddrule: ComponentFixture<AddRuleComponent>;
|
||||
let mockMetadata = {
|
||||
"templates": [
|
||||
{
|
||||
"rule_template": "latestPushedK",
|
||||
"display_text": "the most recently pushed # images",
|
||||
"action": "retain",
|
||||
"params": [
|
||||
{
|
||||
"type": "int",
|
||||
"unit": "COUNT",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rule_template": "latestPulledN",
|
||||
"display_text": "the most recently pulled # images",
|
||||
"action": "retain",
|
||||
"params": [
|
||||
{
|
||||
"type": "int",
|
||||
"unit": "COUNT",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rule_template": "nDaysSinceLastPush",
|
||||
"display_text": "pushed within the last # days",
|
||||
"action": "retain",
|
||||
"params": [
|
||||
{
|
||||
"type": "int",
|
||||
"unit": "DAYS",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rule_template": "nDaysSinceLastPull",
|
||||
"display_text": "pulled within the last # days",
|
||||
"action": "retain",
|
||||
"params": [
|
||||
{
|
||||
"type": "int",
|
||||
"unit": "DAYS",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rule_template": "always",
|
||||
"display_text": "always",
|
||||
"action": "retain",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"scope_selectors": [
|
||||
{
|
||||
"display_text": "Repositories",
|
||||
"kind": "doublestar",
|
||||
"decorations": [
|
||||
"repoMatches",
|
||||
"repoExcludes"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tag_selectors": [
|
||||
{
|
||||
"display_text": "Tags",
|
||||
"kind": "doublestar",
|
||||
"decorations": [
|
||||
"matches",
|
||||
"excludes"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
let mockRules =
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "**"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**"
|
||||
}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"id": 2,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "44"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "555"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "fff**"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**ggg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
let cloneRule = clone(mockRules[0]);
|
||||
cloneRule.tag_selectors[0].pattern = 'rep';
|
||||
let cloneRuleNoId = clone(mockRules[0]);
|
||||
cloneRuleNoId.id = null;
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ImmutableTagComponent, AddRuleComponent, InlineAlertComponent],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
ClarityModule,
|
||||
FormsModule,
|
||||
HttpClientTestingModule,
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
ImmutableTagService,
|
||||
{
|
||||
provide: ActivatedRoute, useValue: {
|
||||
paramMap: of({ get: (key) => 'value' }),
|
||||
snapshot: {
|
||||
parent: {
|
||||
params: { id: 1 }
|
||||
},
|
||||
data: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{ provide: ErrorHandler, useClass: DefaultErrorHandler }
|
||||
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ImmutableTagComponent);
|
||||
fixtureAddrule = TestBed.createComponent(AddRuleComponent);
|
||||
component = fixture.componentInstance;
|
||||
addRuleComponent = fixtureAddrule.componentInstance;
|
||||
addRuleComponent.open = () => {
|
||||
return null;
|
||||
};
|
||||
component.projectId = 1;
|
||||
|
||||
component.addRuleComponent = TestBed.createComponent(AddRuleComponent).componentInstance;
|
||||
component.addRuleComponent = TestBed.createComponent(AddRuleComponent).componentInstance;
|
||||
component.addRuleComponent.open = () => {
|
||||
return null;
|
||||
};
|
||||
component.addRuleComponent.inlineAlert = TestBed.createComponent(InlineAlertComponent).componentInstance;
|
||||
|
||||
immutableTagService = fixture.debugElement.injector.get(ImmutableTagService);
|
||||
errorHandler = fixture.debugElement.injector.get(ErrorHandler);
|
||||
spyOn(immutableTagService, "getRetentionMetadata")
|
||||
.and.returnValue(of(mockMetadata, throwError('error')));
|
||||
spyOn(immutableTagService, "getRules")
|
||||
.withArgs(component.projectId)
|
||||
.and.returnValue(of(mockRules))
|
||||
.withArgs(0)
|
||||
.and.returnValue(throwError('error'));
|
||||
|
||||
spyOn(immutableTagService, "updateRule")
|
||||
.withArgs(component.projectId, mockRules[0])
|
||||
.and.returnValue(of(null))
|
||||
.withArgs(component.projectId, cloneRule)
|
||||
.and.returnValue(of(null));
|
||||
spyOn(immutableTagService, "deleteRule")
|
||||
.withArgs(component.projectId, mockRules[3].id)
|
||||
.and.returnValue(of(null));
|
||||
spyOn(immutableTagService, "createRule")
|
||||
.withArgs(component.projectId, cloneRuleNoId)
|
||||
.and.returnValue(of(null))
|
||||
.withArgs(0, cloneRuleNoId)
|
||||
.and.returnValue(throwError({error: { message: 'error'}}));
|
||||
spyOn(immutableTagService, "getProjectInfo")
|
||||
.withArgs(component.projectId)
|
||||
.and.returnValue(of(null));
|
||||
|
||||
spyOn(errorHandler, "error")
|
||||
.and.returnValue(null);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should show some rules in page", async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let elRep: HTMLLIElement[] = fixture.nativeElement.querySelectorAll(".rule");
|
||||
expect(elRep).toBeTruthy();
|
||||
expect(elRep.length).toEqual(4);
|
||||
});
|
||||
}));
|
||||
it("should show error in list rule", async(() => {
|
||||
fixture.detectChanges();
|
||||
component.projectId = 0;
|
||||
component.getRules();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.projectId = 1;
|
||||
});
|
||||
}));
|
||||
it("should toggle disable and enable", async(() => {
|
||||
fixture.detectChanges();
|
||||
let elRep: HTMLButtonElement = fixture.nativeElement.querySelector("#action0");
|
||||
elRep.dispatchEvent(new Event('click'));
|
||||
elRep.click();
|
||||
let elRepDisable: HTMLButtonElement = fixture.nativeElement.querySelector("#disable-btn0");
|
||||
expect(elRepDisable).toBeTruthy();
|
||||
elRepDisable.dispatchEvent(new Event('click'));
|
||||
elRepDisable.click();
|
||||
mockRules[0].disabled = true;
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let elRepDisableIcon: HTMLButtonElement = fixture.nativeElement.querySelector("#disable-icon0");
|
||||
expect(elRepDisableIcon).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
it("should be deleted", async(() => {
|
||||
fixture.detectChanges();
|
||||
let elRep: HTMLButtonElement = fixture.nativeElement.querySelector("#action0");
|
||||
elRep.dispatchEvent(new Event('click'));
|
||||
elRep.click();
|
||||
let elRepDisable: HTMLButtonElement = fixture.nativeElement.querySelector("#delete-btn3");
|
||||
expect(elRepDisable).toBeTruthy();
|
||||
elRepDisable.dispatchEvent(new Event('click'));
|
||||
elRepDisable.click();
|
||||
let rule = mockRules.pop();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let elRepRule: HTMLLIElement[] = fixture.nativeElement.querySelectorAll(".rule");
|
||||
expect(elRepRule.length).toEqual(3);
|
||||
mockRules.push(rule);
|
||||
});
|
||||
}));
|
||||
|
||||
it("should be add rule", async(() => {
|
||||
fixture.detectChanges();
|
||||
component.clickAdd(cloneRuleNoId);
|
||||
mockRules.push(cloneRuleNoId);
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let elRepRule: HTMLLIElement[] = fixture.nativeElement.querySelectorAll(".rule");
|
||||
expect(elRepRule.length).toEqual(5);
|
||||
mockRules.pop();
|
||||
});
|
||||
|
||||
}));
|
||||
it("should be add rule error", async(() => {
|
||||
fixture.detectChanges();
|
||||
component.projectId = 0;
|
||||
component.clickAdd(cloneRuleNoId);
|
||||
// mockRules.push(cloneRuleNoId);
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.projectId = 1;
|
||||
let elRepRule: HTMLLIElement[] = fixture.nativeElement.querySelectorAll(".rule");
|
||||
expect(elRepRule.length).toEqual(4);
|
||||
// mockRules.pop();
|
||||
});
|
||||
|
||||
}));
|
||||
it("should be edit rule ", async(() => {
|
||||
fixture.detectChanges();
|
||||
component.clickAdd(cloneRule);
|
||||
mockRules[0].tag_selectors[0].pattern = 'rep';
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let elRepRule: HTMLLIElement = fixture.nativeElement.querySelector("#tag-selectors-patten0");
|
||||
expect(elRepRule.textContent).toEqual('rep');
|
||||
mockRules[0].tag_selectors[0].pattern = '**';
|
||||
});
|
||||
|
||||
}));
|
||||
it("should be edit rule with no add", async(() => {
|
||||
fixture.detectChanges();
|
||||
component.addRuleComponent.isAdd = false;
|
||||
component.clickAdd(cloneRule);
|
||||
mockRules[0].tag_selectors[0].pattern = 'rep';
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let elRepRule: HTMLLIElement = fixture.nativeElement.querySelector("#tag-selectors-patten0");
|
||||
expect(elRepRule.textContent).toEqual('rep');
|
||||
mockRules[0].tag_selectors[0].pattern = '**';
|
||||
component.addRuleComponent.isAdd = true;
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
});
|
@ -0,0 +1,169 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AddRuleComponent } from "./add-rule/add-rule.component";
|
||||
import { ImmutableTagService } from "./immutable-tag.service";
|
||||
import { ImmutableRetentionRule } from "../tag-retention/retention";
|
||||
import { clone, ErrorHandler } from "@harbor/ui";
|
||||
import { finalize } from "rxjs/operators";
|
||||
|
||||
@Component({
|
||||
selector: 'app-immutable-tag',
|
||||
templateUrl: './immutable-tag.component.html',
|
||||
styleUrls: ['./immutable-tag.component.scss']
|
||||
})
|
||||
export class ImmutableTagComponent implements OnInit {
|
||||
projectId: number;
|
||||
selectedItem: any = null;
|
||||
ruleIndex: number = -1;
|
||||
index: number = -1;
|
||||
rules: ImmutableRetentionRule[] = [];
|
||||
editIndex: number;
|
||||
loadingRule: boolean = false;
|
||||
|
||||
@ViewChild('addRule', { static: false }) addRuleComponent: AddRuleComponent;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private immutableTagService: ImmutableTagService,
|
||||
public errorHandler: ErrorHandler,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
this.getRules();
|
||||
this.getMetadata();
|
||||
}
|
||||
|
||||
getMetadata() {
|
||||
this.immutableTagService.getRetentionMetadata().subscribe(
|
||||
response => {
|
||||
this.addRuleComponent.metadata = response;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
getRules() {
|
||||
this.immutableTagService.getRules(this.projectId).subscribe(
|
||||
response => {
|
||||
this.rules = response as ImmutableRetentionRule[];
|
||||
this.loadingRule = false;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
this.loadingRule = false;
|
||||
});
|
||||
}
|
||||
|
||||
editRuleByIndex(index) {
|
||||
this.editIndex = index;
|
||||
this.addRuleComponent.rule = clone(this.rules[index]);
|
||||
this.addRuleComponent.editRuleOrigin = clone(this.rules[index]);
|
||||
this.addRuleComponent.open();
|
||||
this.addRuleComponent.isAdd = false;
|
||||
this.ruleIndex = -1;
|
||||
}
|
||||
toggleDisable(rule, isActionDisable) {
|
||||
rule.disabled = isActionDisable;
|
||||
this.ruleIndex = -1;
|
||||
this.loadingRule = true;
|
||||
this.immutableTagService.updateRule(this.projectId, rule).subscribe(
|
||||
response => {
|
||||
this.getRules();
|
||||
}, error => {
|
||||
this.loadingRule = false;
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
deleteRule(ruleId) {
|
||||
// // if rules is empty, clear schedule.
|
||||
this.ruleIndex = -1;
|
||||
this.loadingRule = true;
|
||||
this.immutableTagService.deleteRule(this.projectId, ruleId).subscribe(
|
||||
response => {
|
||||
this.getRules();
|
||||
}, error => {
|
||||
this.loadingRule = false;
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
openAddRule() {
|
||||
this.addRuleComponent.open();
|
||||
this.addRuleComponent.isAdd = true;
|
||||
this.addRuleComponent.rule = new ImmutableRetentionRule(this.projectId);
|
||||
}
|
||||
|
||||
openEditor(index) {
|
||||
if (this.ruleIndex !== index) {
|
||||
this.ruleIndex = index;
|
||||
} else {
|
||||
this.ruleIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
refreshAfterCreatRetention() {
|
||||
this.immutableTagService.getProjectInfo(this.projectId).subscribe(
|
||||
response => {
|
||||
this.getRules();
|
||||
}, error => {
|
||||
this.loadingRule = false;
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
clickAdd(rule) {
|
||||
this.loadingRule = true;
|
||||
this.addRuleComponent.onGoing = true;
|
||||
if (this.addRuleComponent.isAdd) {
|
||||
if (!rule.id) {
|
||||
this.immutableTagService.createRule(this.projectId, rule)
|
||||
.pipe(finalize(() => this.addRuleComponent.onGoing = false)).subscribe(
|
||||
response => {
|
||||
this.refreshAfterCreatRetention();
|
||||
this.addRuleComponent.close();
|
||||
}, error => {
|
||||
if (error && error.error && error.error.message) {
|
||||
error = this.immutableTagService.getI18nKey(error.error.message);
|
||||
}
|
||||
this.addRuleComponent.inlineAlert.showInlineError(error);
|
||||
this.loadingRule = false;
|
||||
});
|
||||
} else {
|
||||
this.immutableTagService.updateRule(this.projectId, rule)
|
||||
.pipe(finalize(() => this.addRuleComponent.onGoing = false)).subscribe(
|
||||
response => {
|
||||
this.getRules();
|
||||
this.addRuleComponent.close();
|
||||
}, error => {
|
||||
this.loadingRule = false;
|
||||
if (error && error.error && error.error.message) {
|
||||
error = this.immutableTagService.getI18nKey(error.error.message);
|
||||
}
|
||||
this.addRuleComponent.inlineAlert.showInlineError(error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.immutableTagService.updateRule(this.projectId, rule)
|
||||
.pipe(finalize(() => this.addRuleComponent.onGoing = false)).subscribe(
|
||||
response => {
|
||||
this.getRules();
|
||||
this.addRuleComponent.close();
|
||||
}, error => {
|
||||
if (error && error.error && error.error.message) {
|
||||
error = this.immutableTagService.getI18nKey(error.error.message);
|
||||
}
|
||||
this.addRuleComponent.inlineAlert.showInlineError(error);
|
||||
this.loadingRule = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
formatPattern(pattern: string): string {
|
||||
return pattern.replace(/[{}]/g, "");
|
||||
}
|
||||
|
||||
getI18nKey(str: string) {
|
||||
return this.immutableTagService.getI18nKey(str);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { ImmutableTagComponent } from './immutable-tag.component';
|
||||
import { ImmutableTagService } from './immutable-tag.service';
|
||||
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddRuleComponent } from './add-rule/add-rule.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ImmutableTagComponent, AddRuleComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
TranslateModule
|
||||
],
|
||||
exports: [
|
||||
|
||||
],
|
||||
providers: [
|
||||
ImmutableTagService
|
||||
]
|
||||
})
|
||||
export class ImmutableTagModule { }
|
@ -0,0 +1,127 @@
|
||||
|
||||
import { ImmutableTagService } from './immutable-tag.service';
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
|
||||
describe('ImmutableTagService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
providers: [ImmutableTagService],
|
||||
imports: [
|
||||
HttpClientTestingModule
|
||||
]
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: ImmutableTagService = TestBed.get(ImmutableTagService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
it('should get rules',
|
||||
inject(
|
||||
[HttpTestingController, ImmutableTagService],
|
||||
(httpMock: HttpTestingController, immutableTagService: ImmutableTagService) => {
|
||||
const mockRules =
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "**"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**"
|
||||
}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"id": 2,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "44"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "555"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"project_id": 1,
|
||||
"disabled": false,
|
||||
"priority": 0,
|
||||
"action": "immutable",
|
||||
"template": "immutable_template",
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "fff**"
|
||||
}
|
||||
],
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**ggg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
immutableTagService.getRules(1).subscribe((res) => {
|
||||
expect(res).toEqual(mockRules);
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
@ -0,0 +1,87 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { ImmutableRetentionRule, RuleMetadate } from "../tag-retention/retention";
|
||||
import { Observable, throwError as observableThrowError } from "rxjs";
|
||||
import { map, catchError } from "rxjs/operators";
|
||||
import { Project } from "../project";
|
||||
import { HTTP_JSON_OPTIONS } from "@harbor/ui";
|
||||
@Injectable()
|
||||
export class ImmutableTagService {
|
||||
private I18nMap: object = {
|
||||
"retain": "ACTION_RETAIN",
|
||||
"lastXDays": "RULE_NAME_1",
|
||||
"latestActiveK": "RULE_NAME_2",
|
||||
"latestPushedK": "RULE_NAME_3",
|
||||
"latestPulledN": "RULE_NAME_4",
|
||||
"always": "RULE_NAME_5",
|
||||
"nDaysSinceLastPull": "RULE_NAME_6",
|
||||
"nDaysSinceLastPush": "RULE_NAME_7",
|
||||
"the images from the last # days": "RULE_TEMPLATE_1",
|
||||
"the most recent active # images": "RULE_TEMPLATE_2",
|
||||
"the most recently pushed # images": "RULE_TEMPLATE_3",
|
||||
"the most recently pulled # images": "RULE_TEMPLATE_4",
|
||||
"pulled within the last # days": "RULE_TEMPLATE_6",
|
||||
"pushed within the last # days": "RULE_TEMPLATE_7",
|
||||
"repoMatches": "MAT",
|
||||
"repoExcludes": "EXC",
|
||||
"matches": "MAT",
|
||||
"excludes": "EXC",
|
||||
"withLabels": "WITH",
|
||||
"withoutLabels": "WITHOUT",
|
||||
"COUNT": "UNIT_COUNT",
|
||||
"DAYS": "UNIT_DAY",
|
||||
"none": "NONE",
|
||||
"nothing": "NONE",
|
||||
"Parameters nDaysSinceLastPull is too large": "DAYS_LARGE",
|
||||
"Parameters nDaysSinceLastPush is too large": "DAYS_LARGE",
|
||||
"Parameters latestPushedK is too large": "COUNT_LARGE",
|
||||
"Parameters latestPulledN is too large": "COUNT_LARGE"
|
||||
};
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
) {
|
||||
}
|
||||
|
||||
getI18nKey(str: string): string {
|
||||
if (this.I18nMap[str.trim()]) {
|
||||
return "TAG_RETENTION." + this.I18nMap[str.trim()];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
getRetentionMetadata(): Observable<RuleMetadate> {
|
||||
return this.http.get(`/api/retentions/metadatas`)
|
||||
.pipe(map(response => response as RuleMetadate))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
getRules(projectId): Observable<ImmutableRetentionRule[]> {
|
||||
return this.http.get(`/api/projects/${projectId}/immutabletagrules`)
|
||||
.pipe(map(response => response as ImmutableRetentionRule[]))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
createRule(projectId: number, retention: ImmutableRetentionRule) {
|
||||
return this.http.post(`/api/projects/${projectId}/immutabletagrules`, retention)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
updateRule(projectId, immutabletagrule: ImmutableRetentionRule) {
|
||||
return this.http.put(`/api/projects/${projectId}/immutabletagrules/${immutabletagrule.id}`, immutabletagrule)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
deleteRule(projectId, ruleId) {
|
||||
|
||||
return this.http.delete(`/api/projects/${projectId}/immutabletagrules/${ruleId}`, HTTP_JSON_OPTIONS)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
getProjectInfo(projectId) {
|
||||
return this.http.get(`/api/projects/${projectId}`)
|
||||
.pipe(map(response => response as Project))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,9 @@
|
||||
<li class="nav-item" *ngIf="hasTagRetentionPermission">
|
||||
<a class="nav-link" routerLink="tag-retention" routerLinkActive="active">{{'TAG_RETENTION.TAG_RETENTION' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="hasTagRetentionPermission">
|
||||
<a class="nav-link" routerLink="immutable-tag" routerLinkActive="active">{{'PROJECT_DETAIL.IMMUTABLE_TAG' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="hasWebhookListPermission">
|
||||
<a class="nav-link" routerLink="webhook" routerLinkActive="active">{{'PROJECT_DETAIL.WEBHOOKS' | translate}}</a>
|
||||
</li>
|
||||
|
@ -18,6 +18,7 @@ import { SharedModule } from '../shared/shared.module';
|
||||
import { RepositoryModule } from '../repository/repository.module';
|
||||
import { ReplicationModule } from '../replication/replication.module';
|
||||
import { SummaryModule } from './summary/summary.module';
|
||||
import { ImmutableTagModule } from './immutable-tag/immutable-tag.module';
|
||||
import { LogModule } from '../log/log.module';
|
||||
|
||||
import { ProjectComponent } from './project.component';
|
||||
@ -58,7 +59,8 @@ import { ConfigScannerService } from "../config/scanner/config-scanner.service";
|
||||
LogModule,
|
||||
RouterModule,
|
||||
HelmChartModule,
|
||||
SummaryModule
|
||||
SummaryModule,
|
||||
ImmutableTagModule
|
||||
],
|
||||
declarations: [
|
||||
ProjectComponent,
|
||||
|
@ -11,8 +11,19 @@
|
||||
// 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.
|
||||
export class Retention {
|
||||
export class BaseRetention {
|
||||
algorithm: string;
|
||||
scope: {
|
||||
level: string,
|
||||
ref: number;
|
||||
};
|
||||
cap: number;
|
||||
|
||||
constructor() {
|
||||
this.algorithm = "or";
|
||||
}
|
||||
}
|
||||
export class Retention extends BaseRetention {
|
||||
rules: Array<Rule>;
|
||||
trigger: {
|
||||
kind: string;
|
||||
@ -21,15 +32,9 @@ export class Retention {
|
||||
cron: string;
|
||||
}
|
||||
};
|
||||
scope: {
|
||||
level: string,
|
||||
ref: number;
|
||||
};
|
||||
cap: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.rules = [];
|
||||
this.algorithm = "or";
|
||||
this.trigger = {
|
||||
kind: "Schedule",
|
||||
references: {},
|
||||
@ -40,13 +45,12 @@ export class Retention {
|
||||
}
|
||||
}
|
||||
|
||||
export class Rule {
|
||||
export class BaseRule {
|
||||
disabled: boolean;
|
||||
template: string;
|
||||
id: number;
|
||||
priority: number;
|
||||
action: string;
|
||||
template: string;
|
||||
params: object;
|
||||
tag_selectors: Array<Selector>;
|
||||
scope_selectors: {
|
||||
repository: Array<Selector>;
|
||||
@ -55,7 +59,6 @@ export class Rule {
|
||||
constructor() {
|
||||
this.disabled = false;
|
||||
this.action = "retain";
|
||||
this.params = {};
|
||||
this.scope_selectors = {
|
||||
repository: [
|
||||
{
|
||||
@ -75,6 +78,27 @@ export class Rule {
|
||||
}
|
||||
}
|
||||
|
||||
export class ImmutableRetentionRule extends BaseRule {
|
||||
project_id: number;
|
||||
constructor(project_id) {
|
||||
super();
|
||||
this.project_id = project_id;
|
||||
this.priority = 0;
|
||||
this.action = 'immutable';
|
||||
this.template = 'immutable_template';
|
||||
}
|
||||
}
|
||||
// rule for tag-retention
|
||||
export class Rule extends BaseRule {
|
||||
|
||||
params: object;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.params = {};
|
||||
}
|
||||
}
|
||||
|
||||
export class Selector {
|
||||
kind: string;
|
||||
decoration: string;
|
||||
|
@ -247,7 +247,8 @@
|
||||
"CONFIG": "Configuration",
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "Robot Accounts",
|
||||
"WEBHOOKS": "Webhooks"
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"IMMUTABLE_TAG": "Immutable Tag"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Project registry",
|
||||
@ -318,10 +319,10 @@
|
||||
"TOKEN": "Token",
|
||||
"NEW_ROBOT_ACCOUNT": "NEW ROBOT ACCOUNT",
|
||||
"ENABLED_STATE": "Enabled state",
|
||||
"NUMBER_REQUIRED":"Field is required and should be an integer other than 0.",
|
||||
"NUMBER_REQUIRED": "Field is required and should be an integer other than 0.",
|
||||
"DESCRIPTION": "Description",
|
||||
"EXPIRATION": "Expiration",
|
||||
"TOKEN_EXPIRATION":"Robot Token Expiration (Days)",
|
||||
"TOKEN_EXPIRATION": "Robot Token Expiration (Days)",
|
||||
"ACTION": "Action",
|
||||
"EDIT": "Edit",
|
||||
"ITEMS": "items",
|
||||
@ -342,8 +343,8 @@
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirm removal of robot accounts",
|
||||
"DELETION_SUMMARY": "Do you want to delete robot accounts {{param}}?",
|
||||
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified.",
|
||||
"EXPORT_TO_FILE" : "export to file"
|
||||
"PULL_IS_MUST": "Pull permission is checked by default and can not be modified.",
|
||||
"EXPORT_TO_FILE": "export to file"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
@ -451,7 +452,7 @@
|
||||
"BOTH": "both",
|
||||
"STOP_SUCCESS": "Stop Execution {{param}} Successful",
|
||||
"STOP_SUMMARY": "Do you want to stop the executions {{param}}?",
|
||||
"TASK_ID":"Task ID",
|
||||
"TASK_ID": "Task ID",
|
||||
"RESOURCE_TYPE": "Resource Type",
|
||||
"SOURCE": "Source",
|
||||
"DESTINATION": "Destination",
|
||||
@ -484,7 +485,7 @@
|
||||
"TESTING_CONNECTION": "Testing Connection...",
|
||||
"TEST_CONNECTION_SUCCESS": "Connection tested successfully.",
|
||||
"TEST_CONNECTION_FAILURE": "Failed to ping endpoint.",
|
||||
"ID":"ID",
|
||||
"ID": "ID",
|
||||
"NAME": "Name",
|
||||
"NAME_IS_REQUIRED": "Name is required.",
|
||||
"DESCRIPTION": "Description",
|
||||
@ -493,7 +494,7 @@
|
||||
"REPLICATION_MODE": "Replication Mode",
|
||||
"SRC_REGISTRY": "Source registry",
|
||||
"DESTINATION_NAMESPACE": "Destination registry:Namespace",
|
||||
"LAST_REPLICATION":"Last Replication",
|
||||
"LAST_REPLICATION": "Last Replication",
|
||||
"DESTINATION_NAME_IS_REQUIRED": "Endpoint name is required.",
|
||||
"NEW_DESTINATION": "New Endpoint",
|
||||
"DESTINATION_URL": "Endpoint URL",
|
||||
@ -549,7 +550,7 @@
|
||||
"SOURCE_RESOURCE_FILTER": "Source resource filter",
|
||||
"SCHEDULED": "Scheduled",
|
||||
"MANUAL": "Manual",
|
||||
"EVENT_BASED":"Event Based",
|
||||
"EVENT_BASED": "Event Based",
|
||||
"DAILY": "Daily",
|
||||
"WEEKLY": "Weekly",
|
||||
"SETTING": "Options",
|
||||
@ -568,14 +569,14 @@
|
||||
"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.",
|
||||
"REPLI_MODE": "Replication mode",
|
||||
"SOURCE_REGISTRY":"Source registry",
|
||||
"SOURCE_NAMESPACES":"Source namespaces",
|
||||
"DEST_REGISTRY":"Destination registry",
|
||||
"DEST_NAMESPACE":"Destination namespace",
|
||||
"SOURCE_REGISTRY": "Source registry",
|
||||
"SOURCE_NAMESPACES": "Source namespaces",
|
||||
"DEST_REGISTRY": "Destination registry",
|
||||
"DEST_NAMESPACE": "Destination namespace",
|
||||
"NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
|
||||
"TAG":"Tag",
|
||||
"LABEL":"Label",
|
||||
"RESOURCE":"Resource"
|
||||
"TAG": "Tag",
|
||||
"LABEL": "Label",
|
||||
"RESOURCE": "Resource"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "New Endpoint",
|
||||
@ -585,7 +586,7 @@
|
||||
"NAME_IS_REQUIRED": "Endpoint name is required.",
|
||||
"URL": "Endpoint URL",
|
||||
"URL_IS_REQUIRED": "Endpoint URL is required.",
|
||||
"AUTHENTICATION":"Authentication",
|
||||
"AUTHENTICATION": "Authentication",
|
||||
"ACCESS_ID": "Access ID",
|
||||
"ACCESS_SECRET": "Access Secret",
|
||||
"STATUS": "Status",
|
||||
@ -816,9 +817,9 @@
|
||||
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
|
||||
"REPO_TOOLTIP": "Users can not do any operations to the images in this mode.",
|
||||
"WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed",
|
||||
"HOURLY_CRON":"Run once an hour, beginning of hour. Equivalent to 0 0 * * * *.",
|
||||
"WEEKLY_CRON":"Run once a week, midnight between Sat/Sun. Equivalent to 0 0 0 * * 0.",
|
||||
"DAILY_CRON":"Run once a day, midnight. Equivalent to 0 0 0 * * *."
|
||||
"HOURLY_CRON": "Run once an hour, beginning of hour. Equivalent to 0 0 * * * *.",
|
||||
"WEEKLY_CRON": "Run once a week, midnight between Sat/Sun. Equivalent to 0 0 0 * * 0.",
|
||||
"DAILY_CRON": "Run once a day, midnight. Equivalent to 0 0 0 * * *."
|
||||
},
|
||||
"LDAP": {
|
||||
"URL": "LDAP URL",
|
||||
|
@ -248,7 +248,8 @@
|
||||
"CONFIG": "Configuración",
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "Robot Accounts",
|
||||
"WEBHOOKS": "Webhooks"
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"IMMUTABLE_TAG": "Immutable Tag"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Registro de proyectos",
|
||||
@ -320,8 +321,8 @@
|
||||
"NEW_ROBOT_ACCOUNT": "NEW ROBOT ACCOUNT",
|
||||
"ENABLED_STATE": "Enabled state",
|
||||
"EXPIRATION": "Expiration",
|
||||
"NUMBER_REQUIRED":"Field is required and should be an integer other than 0.",
|
||||
"TOKEN_EXPIRATION":"Robot Token Expiration (Days)",
|
||||
"NUMBER_REQUIRED": "Field is required and should be an integer other than 0.",
|
||||
"TOKEN_EXPIRATION": "Robot Token Expiration (Days)",
|
||||
"DESCRIPTION": "Description",
|
||||
"ACTION": "Action",
|
||||
"EDIT": "Edit",
|
||||
@ -343,8 +344,8 @@
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirm removal of robot accounts",
|
||||
"DELETION_SUMMARY": "Do you want to delete robot accounts {{param}}?",
|
||||
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified.",
|
||||
"EXPORT_TO_FILE" : "export to file"
|
||||
"PULL_IS_MUST": "Pull permission is checked by default and can not be modified.",
|
||||
"EXPORT_TO_FILE": "export to file"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
@ -451,7 +452,7 @@
|
||||
"PLEASE_SELECT": "select an option",
|
||||
"STOP_SUCCESS": "Stop Execution {{param}} Successful",
|
||||
"STOP_SUMMARY": "De que desea detener las ejecuciones {{param}}?",
|
||||
"TASK_ID":"Task ID",
|
||||
"TASK_ID": "Task ID",
|
||||
"RESOURCE_TYPE": "Resource Type",
|
||||
"SOURCE": "Source",
|
||||
"DESTINATION": "Destination",
|
||||
@ -462,7 +463,7 @@
|
||||
"FAILURE": "FAILURE",
|
||||
"IN_PROGRESS": "IN PROGRESS",
|
||||
"STOP_EXECUTIONS": "Stop Execution",
|
||||
"ID":"ID",
|
||||
"ID": "ID",
|
||||
"REPLICATION_RULE": "Reglas de Replicación",
|
||||
"NEW_REPLICATION_RULE": "Nueva Regla de Replicación",
|
||||
"ENDPOINTS": "Endpoints",
|
||||
@ -495,7 +496,7 @@
|
||||
"REPLICATION_MODE": "Replication Mode",
|
||||
"SRC_REGISTRY": "Source registry",
|
||||
"DESTINATION_NAMESPACE": "Destination registry:Namespace",
|
||||
"LAST_REPLICATION":"Last Replication",
|
||||
"LAST_REPLICATION": "Last Replication",
|
||||
"DESTINATION_NAME_IS_REQUIRED": "El nombre del endpoint es obligatorio.",
|
||||
"NEW_DESTINATION": "Nuevo Endpoint",
|
||||
"DESTINATION_URL": "URL del Endpoint",
|
||||
@ -551,7 +552,7 @@
|
||||
"SOURCE_RESOURCE_FILTER": "Source resource filter",
|
||||
"SCHEDULED": "Scheduled",
|
||||
"MANUAL": "Manual",
|
||||
"EVENT_BASED":"Event Based",
|
||||
"EVENT_BASED": "Event Based",
|
||||
"DAILY": "Daily",
|
||||
"WEEKLY": "Weekly",
|
||||
"SETTING": "Options",
|
||||
@ -569,14 +570,14 @@
|
||||
"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.",
|
||||
"REPLI_MODE": "Replication mode",
|
||||
"SOURCE_REGISTRY":"Source registry",
|
||||
"SOURCE_NAMESPACES":"Source namespaces",
|
||||
"DEST_REGISTRY":"Destination registry",
|
||||
"DEST_NAMESPACE":"Destination namespace",
|
||||
"SOURCE_REGISTRY": "Source registry",
|
||||
"SOURCE_NAMESPACES": "Source namespaces",
|
||||
"DEST_REGISTRY": "Destination registry",
|
||||
"DEST_NAMESPACE": "Destination namespace",
|
||||
"NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
|
||||
"TAG":"Tag",
|
||||
"LABEL":"Label",
|
||||
"RESOURCE":"Resource"
|
||||
"TAG": "Tag",
|
||||
"LABEL": "Label",
|
||||
"RESOURCE": "Resource"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "Nuevo Endpoint",
|
||||
@ -586,7 +587,7 @@
|
||||
"NAME_IS_REQUIRED": "El nombre del endpoint es obligatorio.",
|
||||
"URL": "URL del Endpoint",
|
||||
"URL_IS_REQUIRED": "La URL del endpoint es obligatoria.",
|
||||
"AUTHENTICATION":"Autenticación",
|
||||
"AUTHENTICATION": "Autenticación",
|
||||
"ACCESS_ID": "ID de acceso",
|
||||
"ACCESS_SECRET": "Secreto de acceso",
|
||||
"STATUS": "Estado",
|
||||
@ -815,9 +816,9 @@
|
||||
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
|
||||
"GC_POLICY": "",
|
||||
"WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed",
|
||||
"HOURLY_CRON":"Run once an hour, beginning of hour. Equivalente a 0 0 * * * *.",
|
||||
"WEEKLY_CRON":"Run once a week, midnight between Sat/Sun. Equivalente a 0 0 0 * * 0.",
|
||||
"DAILY_CRON":"Run once a day, midnight. Equivalente a 0 0 0 * * *."
|
||||
"HOURLY_CRON": "Run once an hour, beginning of hour. Equivalente a 0 0 * * * *.",
|
||||
"WEEKLY_CRON": "Run once a week, midnight between Sat/Sun. Equivalente a 0 0 0 * * 0.",
|
||||
"DAILY_CRON": "Run once a day, midnight. Equivalente a 0 0 0 * * *."
|
||||
|
||||
},
|
||||
"LDAP": {
|
||||
|
@ -241,7 +241,8 @@
|
||||
"CONFIG": "Configuration",
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "Robot Accounts",
|
||||
"WEBHOOKS": "Webhooks"
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"IMMUTABLE_TAG": "Immutable Tag"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Dépôt du Projet",
|
||||
@ -311,8 +312,8 @@
|
||||
"NEW_ROBOT_ACCOUNT": "nouveau robot compte ",
|
||||
"ENABLED_STATE": "état d 'activation",
|
||||
"EXPIRATION": "Expiration",
|
||||
"NUMBER_REQUIRED":"Field is required and should be an integer other than 0.",
|
||||
"TOKEN_EXPIRATION":"Robot Token Expiration (Days)",
|
||||
"NUMBER_REQUIRED": "Field is required and should be an integer other than 0.",
|
||||
"TOKEN_EXPIRATION": "Robot Token Expiration (Days)",
|
||||
"DESCRIPTION": "Description",
|
||||
"ACTION": "Action",
|
||||
"EDIT": "Edit",
|
||||
@ -335,8 +336,8 @@
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "confirmer l'enlèvement des comptes du robot ",
|
||||
"DELETION_SUMMARY": "Voulez-vous supprimer la règle {{param}}?",
|
||||
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified.",
|
||||
"EXPORT_TO_FILE" : "export to file"
|
||||
"PULL_IS_MUST": "Pull permission is checked by default and can not be modified.",
|
||||
"EXPORT_TO_FILE": "export to file"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
@ -444,7 +445,7 @@
|
||||
"PLEASE_SELECT": "select an option",
|
||||
"STOP_SUCCESS": "Stop Execution {{param}} Successful",
|
||||
"STOP_SUMMARY": "Voulez-vous arrêter les exécutions {{param}}?",
|
||||
"TASK_ID":"Task ID",
|
||||
"TASK_ID": "Task ID",
|
||||
"RESOURCE_TYPE": "Resource Type",
|
||||
"SOURCE": "Source",
|
||||
"DESTINATION": "Destination",
|
||||
@ -455,7 +456,7 @@
|
||||
"FAILURE": "FAILURE",
|
||||
"IN_PROGRESS": "IN PROGRESS",
|
||||
"STOP_EXECUTIONS": "Stop Execution",
|
||||
"ID":"ID",
|
||||
"ID": "ID",
|
||||
"REPLICATION_RULE": "Règle de Réplication",
|
||||
"NEW_REPLICATION_RULE": "Nouvelle Règle de Réplication",
|
||||
"ENDPOINTS": "Points finaux",
|
||||
@ -485,7 +486,7 @@
|
||||
"REPLICATION_MODE": "Replication Mode",
|
||||
"SRC_REGISTRY": "Source registry",
|
||||
"DESTINATION_NAMESPACE": "Destination registry:Namespace",
|
||||
"LAST_REPLICATION":"Last Replication",
|
||||
"LAST_REPLICATION": "Last Replication",
|
||||
"DESTINATION_NAME_IS_REQUIRED": "Le nom du Point Final est obligatoire.",
|
||||
"NEW_DESTINATION": "Nouveau Point Final",
|
||||
"DESTINATION_URL": "URL du Point Final",
|
||||
@ -540,7 +541,7 @@
|
||||
"SOURCE_RESOURCE_FILTER": "Source resource filter",
|
||||
"SCHEDULED": "Scheduled",
|
||||
"MANUAL": "Manual",
|
||||
"EVENT_BASED":"Event Based",
|
||||
"EVENT_BASED": "Event Based",
|
||||
"DAILY": "Daily",
|
||||
"WEEKLY": "Weekly",
|
||||
"SETTING": "Options",
|
||||
@ -559,14 +560,14 @@
|
||||
"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.",
|
||||
"REPLI_MODE": "Replication mode",
|
||||
"SOURCE_REGISTRY":"Source registry",
|
||||
"SOURCE_NAMESPACES":"Source namespaces",
|
||||
"DEST_REGISTRY":"Destination registry",
|
||||
"DEST_NAMESPACE":"Destination namespace",
|
||||
"SOURCE_REGISTRY": "Source registry",
|
||||
"SOURCE_NAMESPACES": "Source namespaces",
|
||||
"DEST_REGISTRY": "Destination registry",
|
||||
"DEST_NAMESPACE": "Destination namespace",
|
||||
"NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
|
||||
"TAG":"Tag",
|
||||
"LABEL":"Label",
|
||||
"RESOURCE":"Resource"
|
||||
"TAG": "Tag",
|
||||
"LABEL": "Label",
|
||||
"RESOURCE": "Resource"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "Nouveau Point Final",
|
||||
@ -576,7 +577,7 @@
|
||||
"NAME_IS_REQUIRED": "Le nom du Point final est obligatoire.",
|
||||
"URL": "URL du Point Final",
|
||||
"URL_IS_REQUIRED": "L'URL du Point Final est obligatoire.",
|
||||
"AUTHENTICATION":"Authentification",
|
||||
"AUTHENTICATION": "Authentification",
|
||||
"ACCESS_ID": "ID d'accès",
|
||||
"ACCESS_SECRET": "Secret d'accès",
|
||||
"STATUS": "Statut",
|
||||
@ -796,9 +797,9 @@
|
||||
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
|
||||
"GC_POLICY": "",
|
||||
"WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed",
|
||||
"HOURLY_CRON":"Run once an hour, beginning of hour. Équivalent à 0 0 * * * *.",
|
||||
"WEEKLY_CRON":"Run once a week, midnight between Sat/Sun. Équivalent à 0 0 0 * * 0.",
|
||||
"DAILY_CRON":"Run once a day, midnight. Équivalent à 0 0 0 * * *."
|
||||
"HOURLY_CRON": "Run once an hour, beginning of hour. Équivalent à 0 0 * * * *.",
|
||||
"WEEKLY_CRON": "Run once a week, midnight between Sat/Sun. Équivalent à 0 0 0 * * 0.",
|
||||
"DAILY_CRON": "Run once a day, midnight. Équivalent à 0 0 0 * * *."
|
||||
},
|
||||
"LDAP": {
|
||||
"URL": "URL LDAP",
|
||||
|
@ -245,7 +245,8 @@
|
||||
"CONFIG": "Configuração",
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "Robot Accounts",
|
||||
"WEBHOOKS": "Webhooks"
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"IMMUTABLE_TAG": "Immutable Tag"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Registro do Projeto",
|
||||
@ -317,8 +318,8 @@
|
||||
"NEW_ROBOT_ACCOUNT": "Novo robô conta",
|
||||
"ENABLED_STATE": "Enabled state",
|
||||
"EXPIRATION": "Expiration",
|
||||
"NUMBER_REQUIRED":"Field is required and should be an integer other than 0.",
|
||||
"TOKEN_EXPIRATION":"Robot Token Expiration (Days)",
|
||||
"NUMBER_REQUIRED": "Field is required and should be an integer other than 0.",
|
||||
"TOKEN_EXPIRATION": "Robot Token Expiration (Days)",
|
||||
"DESCRIPTION": "Descrição",
|
||||
"ACTION": "AÇÃO",
|
||||
"EDIT": "Editar",
|
||||
@ -340,8 +341,8 @@
|
||||
"COPY_SUCCESS": "Copy token successfully of '{{param}}'",
|
||||
"DELETION_TITLE": "Confirmar a remoção do robô Contas",
|
||||
"DELETION_SUMMARY": "Você quer remover a regra {{param}}?",
|
||||
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified.",
|
||||
"EXPORT_TO_FILE" : "export to file"
|
||||
"PULL_IS_MUST": "Pull permission is checked by default and can not be modified.",
|
||||
"EXPORT_TO_FILE": "export to file"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Grupo",
|
||||
@ -449,7 +450,7 @@
|
||||
"PLEASE_SELECT": "select an option",
|
||||
"STOP_SUCCESS": "Stop Execution {{param}} Successful",
|
||||
"STOP_SUMMARY": "Você quer parar as execuções? {{param}}?",
|
||||
"TASK_ID":"Task ID",
|
||||
"TASK_ID": "Task ID",
|
||||
"RESOURCE_TYPE": "Resource Type",
|
||||
"SOURCE": "Source",
|
||||
"DESTINATION": "Destination",
|
||||
@ -460,7 +461,7 @@
|
||||
"FAILURE": "FAILURE",
|
||||
"IN_PROGRESS": "IN PROGRESS",
|
||||
"STOP_EXECUTIONS": "Stop Execution",
|
||||
"ID":"ID",
|
||||
"ID": "ID",
|
||||
"REPLICATION_RULE": "Regra de replicação",
|
||||
"NEW_REPLICATION_RULE": "Nova regra de replicação",
|
||||
"ENDPOINTS": "Endpoints",
|
||||
@ -493,7 +494,7 @@
|
||||
"REPLICATION_MODE": "Replication Mode",
|
||||
"SRC_REGISTRY": "Source registry",
|
||||
"DESTINATION_NAMESPACE": "Destination registry:Namespace",
|
||||
"LAST_REPLICATION":"Last Replication",
|
||||
"LAST_REPLICATION": "Last Replication",
|
||||
"DESTINATION_NAME_IS_REQUIRED": "Nome do Endpoint é obrigatório.",
|
||||
"NEW_DESTINATION": "Novo Endpoint",
|
||||
"DESTINATION_URL": "URL do Endpoint",
|
||||
@ -549,33 +550,33 @@
|
||||
"SOURCE_RESOURCE_FILTER": "Source resource filter",
|
||||
"SCHEDULED": "Agendado",
|
||||
"MANUAL": "Manual",
|
||||
"EVENT_BASED":"Event Based",
|
||||
"EVENT_BASED": "Event Based",
|
||||
"DAILY": "Diário",
|
||||
"WEEKLY": "Semanal",
|
||||
"SETTING":"Opções",
|
||||
"TRIGGER":"Condições de disparo",
|
||||
"TARGETS":"Destino",
|
||||
"SETTING": "Opções",
|
||||
"TRIGGER": "Condições de disparo",
|
||||
"TARGETS": "Destino",
|
||||
"MODE": "Modo",
|
||||
"TRIGGER_MODE": "Modo de disparo",
|
||||
"SOURCE_PROJECT": "Projeto de origem",
|
||||
"REPLICATE": "Replicar",
|
||||
"DELETE_REMOTE_IMAGES":"Remover resources remotas quando removido localmente",
|
||||
"DELETE_REMOTE_IMAGES": "Remover resources remotas quando removido localmente",
|
||||
"DELETE_ENABLED": "Enabled this policy",
|
||||
"REPLICATE_IMMEDIATE":"Replicar imagens existentes imediatamente",
|
||||
"REPLICATE_IMMEDIATE": "Replicar imagens existentes imediatamente",
|
||||
"NEW": "Novo",
|
||||
"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.",
|
||||
"DESTINATION_NAME_TOOLTIP": "Destination name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
|
||||
"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.",
|
||||
"REPLI_MODE": "Replication mode",
|
||||
"SOURCE_REGISTRY":"Source registry",
|
||||
"SOURCE_NAMESPACES":"Source namespaces",
|
||||
"DEST_REGISTRY":"Destination registry",
|
||||
"DEST_NAMESPACE":"Destination namespace",
|
||||
"SOURCE_REGISTRY": "Source registry",
|
||||
"SOURCE_NAMESPACES": "Source namespaces",
|
||||
"DEST_REGISTRY": "Destination registry",
|
||||
"DEST_NAMESPACE": "Destination namespace",
|
||||
"NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
|
||||
"TAG":"Tag",
|
||||
"LABEL":"Label",
|
||||
"RESOURCE":"Resource"
|
||||
"TAG": "Tag",
|
||||
"LABEL": "Label",
|
||||
"RESOURCE": "Resource"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "Novo Endpoint",
|
||||
@ -585,7 +586,7 @@
|
||||
"NAME_IS_REQUIRED": "Nome do Endpoint é obrigatório.",
|
||||
"URL": "URL do Endpoint",
|
||||
"URL_IS_REQUIRED": "URL do Endpoint URL é obrigatória.",
|
||||
"AUTHENTICATION":"Autenticação",
|
||||
"AUTHENTICATION": "Autenticação",
|
||||
"ACCESS_ID": "ID de acesso",
|
||||
"ACCESS_SECRET": "Secreto de acceso",
|
||||
"STATUS": "Segredo de acesso",
|
||||
@ -810,9 +811,9 @@
|
||||
"READONLY_TOOLTIP": "Em modo somente leitura, você não pode remover repositórios ou tags ou enviar imagens. ",
|
||||
"REPO_TOOLTIP": "Usuários não podem efetuar qualquer operação nas imagens nesse modo.",
|
||||
"WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed",
|
||||
"HOURLY_CRON":"Run once an hour, beginning of hour. Equivalente a 0 0 * * * *.",
|
||||
"WEEKLY_CRON":"Run once a week, midnight between Sat/Sun. Equivalente a 0 0 0 * * 0.",
|
||||
"DAILY_CRON":"Run once a day, midnight. Equivalente a 0 0 0 * * *."
|
||||
"HOURLY_CRON": "Run once an hour, beginning of hour. Equivalente a 0 0 * * * *.",
|
||||
"WEEKLY_CRON": "Run once a week, midnight between Sat/Sun. Equivalente a 0 0 0 * * 0.",
|
||||
"DAILY_CRON": "Run once a day, midnight. Equivalente a 0 0 0 * * *."
|
||||
},
|
||||
"LDAP": {
|
||||
"URL": "URL LDAP",
|
||||
@ -1074,15 +1075,15 @@
|
||||
"SERVER_ERROR": "Não foi possível executar suas ações pois ocorreram erros internos.",
|
||||
"INCONRRECT_OLD_PWD": "A senha antiga está incorreta.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS":"Status",
|
||||
"STATUS": "Status",
|
||||
"START_TIME": "Início",
|
||||
"END_TIME": "Finalização",
|
||||
"DETAILS":"Detalhes",
|
||||
"PENDING":"Pendente",
|
||||
"FINISHED":"Finalizado",
|
||||
"STOPPED":"Parado",
|
||||
"RUNNING":"Executando",
|
||||
"ERROR":"Erro",
|
||||
"DETAILS": "Detalhes",
|
||||
"PENDING": "Pendente",
|
||||
"FINISHED": "Finalizado",
|
||||
"STOPPED": "Parado",
|
||||
"RUNNING": "Executando",
|
||||
"ERROR": "Erro",
|
||||
"SCHEDULE": {
|
||||
"NONE": "Nenhum",
|
||||
"DAILY": "Diário",
|
||||
@ -1102,14 +1103,14 @@
|
||||
"AT": "em",
|
||||
"GC_NOW": "GC AGORA",
|
||||
"JOB_HISTORY": "GC History",
|
||||
"JOB_LIST":"Lista de tarefas de Limpeza",
|
||||
"JOB_ID":"ID DA TAREFA",
|
||||
"JOB_LIST": "Lista de tarefas de Limpeza",
|
||||
"JOB_ID": "ID DA TAREFA",
|
||||
"TRIGGER_TYPE": "TIPO DE DISPARO",
|
||||
"LATEST_JOBS": "Ultimas tarefas {{param}}",
|
||||
"LOG_DETAIL":"Detalhes de Log",
|
||||
"MSG_SUCCESS":"Garbage Collection efetuado com sucesso",
|
||||
"MSG_SCHEDULE_SET":"Agendamento de Garbage Collection efetuado",
|
||||
"MSG_SCHEDULE_RESET":"Agendamento de Garbage Collection foi redefinido"
|
||||
"LOG_DETAIL": "Detalhes de Log",
|
||||
"MSG_SUCCESS": "Garbage Collection efetuado com sucesso",
|
||||
"MSG_SCHEDULE_SET": "Agendamento de Garbage Collection efetuado",
|
||||
"MSG_SCHEDULE_RESET": "Agendamento de Garbage Collection foi redefinido"
|
||||
},
|
||||
"RETAG": {
|
||||
"MSG_SUCCESS": "Retag successfully",
|
||||
|
@ -247,7 +247,8 @@
|
||||
"CONFIG": "Ayarlar",
|
||||
"HELMCHART": "Helm Tabloları",
|
||||
"ROBOT_ACCOUNTS": "Robot Hesapları",
|
||||
"WEBHOOKS": "Ağ Kancaları"
|
||||
"WEBHOOKS": "Ağ Kancaları",
|
||||
"IMMUTABLE_TAG": "Immutable Tag"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Proje kaydı",
|
||||
@ -318,10 +319,10 @@
|
||||
"TOKEN": "Token",
|
||||
"NEW_ROBOT_ACCOUNT": "YENİ ROBOT HESABI",
|
||||
"ENABLED_STATE": "Etkin durum",
|
||||
"NUMBER_REQUIRED":"Alan zorunludur ve 0 dışında bir tam sayı olmalıdır.",
|
||||
"NUMBER_REQUIRED": "Alan zorunludur ve 0 dışında bir tam sayı olmalıdır.",
|
||||
"DESCRIPTION": "Açıklama",
|
||||
"EXPIRATION": "Süre Sonu",
|
||||
"TOKEN_EXPIRATION":"Robot Token Sona Ermesi (Günler)",
|
||||
"TOKEN_EXPIRATION": "Robot Token Sona Ermesi (Günler)",
|
||||
"ACTION": "Eylem",
|
||||
"EDIT": "Düzenle",
|
||||
"ITEMS": "parça",
|
||||
@ -342,8 +343,8 @@
|
||||
"COPY_SUCCESS": "Token başarıyla kopyala '{{param}}'",
|
||||
"DELETION_TITLE": "Robot hesaplarının kaldırılmasını onaylayın",
|
||||
"DELETION_SUMMARY": "Robot hesaplarını silmek istiyor musunuz {{param}}?",
|
||||
"PULL_IS_MUST" : "Çekme izni varsayılan olarak kontrol edilir ve değiştirilemez.",
|
||||
"EXPORT_TO_FILE" : "dosyayı dışarı aktar"
|
||||
"PULL_IS_MUST": "Çekme izni varsayılan olarak kontrol edilir ve değiştirilemez.",
|
||||
"EXPORT_TO_FILE": "dosyayı dışarı aktar"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "DÜZENLE",
|
||||
@ -450,7 +451,7 @@
|
||||
"BOTH": "her ikisi de",
|
||||
"STOP_SUCCESS": "Yürütmeyi Durdur {{param}} Başarılı",
|
||||
"STOP_SUMMARY": "Yürütmeyi durdurmayı iptal istiyor musunuz? {{param}}?",
|
||||
"TASK_ID":"Görev ID",
|
||||
"TASK_ID": "Görev ID",
|
||||
"RESOURCE_TYPE": "Kaynak tipi",
|
||||
"SOURCE": "Kaynak",
|
||||
"DESTINATION": "Hedef",
|
||||
@ -483,7 +484,7 @@
|
||||
"TESTING_CONNECTION": "Bağlantı Testi...",
|
||||
"TEST_CONNECTION_SUCCESS": "Bağlantı başarıyla test edildi.",
|
||||
"TEST_CONNECTION_FAILURE": "Ping atılamadı",
|
||||
"ID":"ID",
|
||||
"ID": "ID",
|
||||
"NAME": "İsim",
|
||||
"NAME_IS_REQUIRED": "İsim gerekli.",
|
||||
"DESCRIPTION": "Açıklama",
|
||||
@ -492,7 +493,7 @@
|
||||
"REPLICATION_MODE": "Çoğaltma modu",
|
||||
"SRC_REGISTRY": "Kaynak kaydı",
|
||||
"DESTINATION_NAMESPACE": "Hedef kayıt defteri: Ad alanı",
|
||||
"LAST_REPLICATION":"Son Çoğaltma",
|
||||
"LAST_REPLICATION": "Son Çoğaltma",
|
||||
"DESTINATION_NAME_IS_REQUIRED": "Uç noktası adı gerekli.",
|
||||
"NEW_DESTINATION": "Yeni Uç Noktası",
|
||||
"DESTINATION_URL": "Uç Noktası URL",
|
||||
@ -548,7 +549,7 @@
|
||||
"SOURCE_RESOURCE_FILTER": "Kaynak vasıtası filtresi",
|
||||
"SCHEDULED": "Belirlenmiş",
|
||||
"MANUAL": "Manuel",
|
||||
"EVENT_BASED":"Etkinlik Tabanı",
|
||||
"EVENT_BASED": "Etkinlik Tabanı",
|
||||
"DAILY": "Günlük",
|
||||
"WEEKLY": "Haftalık",
|
||||
"SETTING": "Ayarlar",
|
||||
@ -567,14 +568,14 @@
|
||||
"ACKNOWLEDGE": "Onaylamak",
|
||||
"RULE_DISABLED": "Bu kural, filtresinde kullanılan bir etiket silindiğinden dolayı devre dışı bırakıldı. \n Kuralı düzenleyin ve etkinleştirmek için filtresini güncelleyin.",
|
||||
"REPLI_MODE": "Çoğaltma modu",
|
||||
"SOURCE_REGISTRY":"Kaynak kaydı",
|
||||
"SOURCE_NAMESPACES":"Kaynak ad alanları",
|
||||
"DEST_REGISTRY":"Hedef kaydı",
|
||||
"DEST_NAMESPACE":"Hedef ad alanı",
|
||||
"SOURCE_REGISTRY": "Kaynak kaydı",
|
||||
"SOURCE_NAMESPACES": "Kaynak ad alanları",
|
||||
"DEST_REGISTRY": "Hedef kaydı",
|
||||
"DEST_NAMESPACE": "Hedef ad alanı",
|
||||
"NAMESPACE_TOOLTIP": "İsim alanı ismi en az 2 karakter uzunluğunda, küçük harfli karakterler, sayılar ve ._- ile başlamalı ve karakter veya sayılarla başlamalıdır. Ad alanı adı en az 2 karakter uzunluğunda, küçük harf, rakam ve ._- ile başlamalı ve karakter veya rakamlarla başlamalıdır.",
|
||||
"TAG":"Etiketlemek",
|
||||
"LABEL":"Etiket",
|
||||
"RESOURCE":"Kaynak"
|
||||
"TAG": "Etiketlemek",
|
||||
"LABEL": "Etiket",
|
||||
"RESOURCE": "Kaynak"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "Yeni Uç Nokta",
|
||||
@ -584,7 +585,7 @@
|
||||
"NAME_IS_REQUIRED": "Uç noktası adı gerekli.",
|
||||
"URL": "Uç noktası adı gerekli.",
|
||||
"URL_IS_REQUIRED": "Uç noktası URL gerekli.",
|
||||
"AUTHENTICATION":"Doğrulama",
|
||||
"AUTHENTICATION": "Doğrulama",
|
||||
"ACCESS_ID": "Erişim kimliği",
|
||||
"ACCESS_SECRET": "Erişim Şifresi",
|
||||
"STATUS": "Durum",
|
||||
@ -815,9 +816,9 @@
|
||||
"READONLY_TOOLTIP": "Salt okunur modda, depoları veya imajları silemez veya görüntüleri yükleyemezsiniz.",
|
||||
"REPO_TOOLTIP": "Kullanıcılar bu moddaki imajlarda hiçbir işlem yapamazlar.",
|
||||
"WEBHOOK_TOOLTIP": "Görüntü veya grafik olarak basılan, çekilmiş, silinen, taranan gibi bazı eylemler gerçekleştirildiğinde ağ kancalarının belirtilen bitiş noktalarınızdan geri aramalar almasını sağlayın",
|
||||
"HOURLY_CRON":"Saat başı bir saat, saat başı çalıştırın. Eşittir 0 0 * * * *.",
|
||||
"WEEKLY_CRON":"Haftada bir kez, gece yarısı Cmt / Paz arasında koşun. Eşittir 0 0 0 * * 0.",
|
||||
"DAILY_CRON":"Günde bir kez, gece yarısı çalıştırın. Eşittir0 0 0 * * *."
|
||||
"HOURLY_CRON": "Saat başı bir saat, saat başı çalıştırın. Eşittir 0 0 * * * *.",
|
||||
"WEEKLY_CRON": "Haftada bir kez, gece yarısı Cmt / Paz arasında koşun. Eşittir 0 0 0 * * 0.",
|
||||
"DAILY_CRON": "Günde bir kez, gece yarısı çalıştırın. Eşittir0 0 0 * * *."
|
||||
},
|
||||
"LDAP": {
|
||||
"URL": "LDAP URL",
|
||||
|
@ -246,7 +246,8 @@
|
||||
"CONFIG": "配置管理",
|
||||
"HELMCHART": "Helm Charts",
|
||||
"ROBOT_ACCOUNTS": "机器人账户",
|
||||
"WEBHOOKS": "Webhooks"
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"IMMUTABLE_TAG": "不变的Tag"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "项目仓库",
|
||||
@ -318,8 +319,8 @@
|
||||
"NEW_ROBOT_ACCOUNT": "添加机器人账户",
|
||||
"ENABLED_STATE": "启用状态",
|
||||
"EXPIRATION": "过期时间",
|
||||
"NUMBER_REQUIRED":"此项为必填项且为不为0的整数。",
|
||||
"TOKEN_EXPIRATION":"机器人账户令牌过期时间(天)",
|
||||
"NUMBER_REQUIRED": "此项为必填项且为不为0的整数。",
|
||||
"TOKEN_EXPIRATION": "机器人账户令牌过期时间(天)",
|
||||
"DESCRIPTION": "描述",
|
||||
"ACTION": "操作",
|
||||
"EDIT": "编辑",
|
||||
@ -341,8 +342,8 @@
|
||||
"COPY_SUCCESS": "成功复制 '{{param}}' 的令牌",
|
||||
"DELETION_TITLE": "删除账户确认",
|
||||
"DELETION_SUMMARY": "你确认删除机器人账户 {{param}}?",
|
||||
"PULL_IS_MUST" : "拉取权限默认选中且不可修改。",
|
||||
"EXPORT_TO_FILE" : "导出到文件中"
|
||||
"PULL_IS_MUST": "拉取权限默认选中且不可修改。",
|
||||
"EXPORT_TO_FILE": "导出到文件中"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "编辑",
|
||||
@ -450,7 +451,7 @@
|
||||
"PLEASE_SELECT": "请选择",
|
||||
"STOP_SUCCESS": "停止任务 {{param}} 成功",
|
||||
"STOP_SUMMARY": "确认停止任务{{param}}?",
|
||||
"TASK_ID":"任务ID",
|
||||
"TASK_ID": "任务ID",
|
||||
"RESOURCE_TYPE": "源类型",
|
||||
"SOURCE": "源",
|
||||
"DESTINATION": "目标",
|
||||
@ -461,7 +462,7 @@
|
||||
"FAILURE": "失败",
|
||||
"IN_PROGRESS": "进行中",
|
||||
"STOP_EXECUTIONS": "停止任务",
|
||||
"ID":"ID",
|
||||
"ID": "ID",
|
||||
"REPLICATION_RULE": "同步规则",
|
||||
"NEW_REPLICATION_RULE": "新建规则",
|
||||
"ENDPOINTS": "目标",
|
||||
@ -494,7 +495,7 @@
|
||||
"REPLICATION_MODE": "同步模式",
|
||||
"SRC_REGISTRY": "源仓库",
|
||||
"DESTINATION_NAMESPACE": "目标仓库:命名空间",
|
||||
"LAST_REPLICATION":"最后一次同步",
|
||||
"LAST_REPLICATION": "最后一次同步",
|
||||
"DESTINATION_NAME_IS_REQUIRED": "目标名称为必填项。",
|
||||
"NEW_DESTINATION": "创建目标",
|
||||
"DESTINATION_URL": "目标URL",
|
||||
@ -550,7 +551,7 @@
|
||||
"SOURCE_RESOURCE_FILTER": "源资源过滤器",
|
||||
"SCHEDULED": "定时",
|
||||
"MANUAL": "手动",
|
||||
"EVENT_BASED":"事件驱动",
|
||||
"EVENT_BASED": "事件驱动",
|
||||
"DAILY": "每天",
|
||||
"WEEKLY": "每周",
|
||||
"SETTING": "设置",
|
||||
@ -569,14 +570,14 @@
|
||||
"ACKNOWLEDGE": "确认",
|
||||
"RULE_DISABLED": "这个规则因为过滤选项中的标签被删除已经不能用了,更新过滤项以便重新启用规则。",
|
||||
"REPLI_MODE": "同步模式",
|
||||
"SOURCE_REGISTRY":"源Registry",
|
||||
"SOURCE_NAMESPACES":"源Namespace",
|
||||
"DEST_REGISTRY":"目的Registry",
|
||||
"DEST_NAMESPACE":"目的Namespace",
|
||||
"SOURCE_REGISTRY": "源Registry",
|
||||
"SOURCE_NAMESPACES": "源Namespace",
|
||||
"DEST_REGISTRY": "目的Registry",
|
||||
"DEST_NAMESPACE": "目的Namespace",
|
||||
"NAMESPACE_TOOLTIP": "Namespace名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
|
||||
"TAG":"Tag",
|
||||
"LABEL":"标签",
|
||||
"RESOURCE":"资源"
|
||||
"TAG": "Tag",
|
||||
"LABEL": "标签",
|
||||
"RESOURCE": "资源"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "新建目标",
|
||||
@ -586,7 +587,7 @@
|
||||
"NAME_IS_REQUIRED": "目标名为必填项。",
|
||||
"URL": "目标URL",
|
||||
"URL_IS_REQUIRED": "目标URL为必填项。",
|
||||
"AUTHENTICATION":"认证",
|
||||
"AUTHENTICATION": "认证",
|
||||
"ACCESS_ID": "访问ID",
|
||||
"ACCESS_SECRET": "访问密码",
|
||||
"STATUS": "状态",
|
||||
@ -816,9 +817,9 @@
|
||||
"READONLY_TOOLTIP": "选中,表示正在维护状态,不可删除仓库及标签,也不可以推送镜像。",
|
||||
"REPO_TOOLTIP": "用户在此模式下无法对镜像执行任何操作。",
|
||||
"WEBHOOK_TOOLTIP": "当执行推送,拉动,删除,扫描镜像或 chart 等特定操作时,启用 webhooks 以在指定端点接收回调",
|
||||
"HOURLY_CRON":"每小时运行一次。相当于 0 0 * * * *",
|
||||
"WEEKLY_CRON":"每周一次,周六/周日午夜之间开始。相当于 0 0 * * * *",
|
||||
"DAILY_CRON":"每天午夜运行一次。相当于 0 0 * * * *"
|
||||
"HOURLY_CRON": "每小时运行一次。相当于 0 0 * * * *",
|
||||
"WEEKLY_CRON": "每周一次,周六/周日午夜之间开始。相当于 0 0 * * * *",
|
||||
"DAILY_CRON": "每天午夜运行一次。相当于 0 0 * * * *"
|
||||
},
|
||||
"LDAP": {
|
||||
"URL": "LDAP URL",
|
||||
|
Loading…
Reference in New Issue
Block a user