mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-19 16:55:16 +01:00
Merge pull request #8392 from AllForNothing/tag-retention
add tag-retention tab to project detail Ignore codacy at present.
This commit is contained in:
commit
dcc79ca2a4
@ -104,8 +104,8 @@ describe('ProjectPolicyConfigComponent', () => {
|
||||
ProjectPolicyConfigComponent,
|
||||
ConfirmationDialogComponent,
|
||||
ConfirmationDialogComponent,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: ProjectService, useClass: ProjectDefaultService },
|
||||
@ -113,7 +113,7 @@ describe('ProjectPolicyConfigComponent', () => {
|
||||
{ provide: UserPermissionService, useClass: UserPermissionDefaultService},
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -131,9 +131,9 @@ describe('ProjectPolicyConfigComponent', () => {
|
||||
|
||||
userPermissionService = fixture.debugElement.injector.get(UserPermissionService);
|
||||
spyOn(userPermissionService, "getPermission")
|
||||
.withArgs(component.projectId,
|
||||
USERSTATICPERMISSION.CONFIGURATION.KEY, USERSTATICPERMISSION.CONFIGURATION.VALUE.UPDATE )
|
||||
.and.returnValue(of(mockHasChangeConfigRole));
|
||||
.withArgs(component.projectId,
|
||||
USERSTATICPERMISSION.CONFIGURATION.KEY, USERSTATICPERMISSION.CONFIGURATION.VALUE.UPDATE )
|
||||
.and.returnValue(of(mockHasChangeConfigRole));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
@ -106,11 +106,12 @@ export class ProjectPolicyConfigComponent implements OnInit {
|
||||
this.systemInfoService.getSystemInfo()
|
||||
.subscribe(systemInfo => {
|
||||
this.systemInfo = systemInfo;
|
||||
setTimeout(() => {
|
||||
this.dateSystemInput.nativeElement.parentNode.setAttribute("hidden", "hidden");
|
||||
}, 100);
|
||||
if (this.withClair) {
|
||||
setTimeout(() => {
|
||||
this.dateSystemInput.nativeElement.parentNode.setAttribute("hidden", "hidden");
|
||||
}, 100);
|
||||
}
|
||||
} , error => this.errorHandler.error(error));
|
||||
|
||||
// retrive project level policy data
|
||||
this.retrieve();
|
||||
this.getPermission();
|
||||
|
@ -132,5 +132,17 @@ export const USERSTATICPERMISSION = {
|
||||
"READ": "read",
|
||||
}
|
||||
},
|
||||
"TAG_RETENTION": {
|
||||
'KEY': "tag-retention",
|
||||
'VALUE': {
|
||||
"CREATE": "create",
|
||||
"UPDATE": "update",
|
||||
"DELETE": "delete",
|
||||
"LIST": "list",
|
||||
"READ": "read",
|
||||
"PULL": "pull",
|
||||
"PUSH": "push"
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -57,6 +57,8 @@ import { ListChartVersionsComponent } from './project/helm-chart/list-chart-vers
|
||||
import { HelmChartDetailComponent } from './project/helm-chart/helm-chart-detail/chart-detail.component';
|
||||
import { OidcOnboardComponent } from './oidc-onboard/oidc-onboard.component';
|
||||
import { SummaryComponent } from './project/summary/summary.component';
|
||||
import { TagRetentionComponent } from "./project/tag-retention/tag-retention.component";
|
||||
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
@ -201,6 +203,10 @@ const harborRoutes: Routes = [
|
||||
{
|
||||
path: 'robot-account',
|
||||
component: RobotAccountComponent
|
||||
},
|
||||
{
|
||||
path: 'tag-retention',
|
||||
component: TagRetentionComponent
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -25,6 +25,9 @@
|
||||
<li class="nav-item" *ngIf="hasRobotListPermission">
|
||||
<a class="nav-link" routerLink="robot-account" routerLinkActive="active">{{'PROJECT_DETAIL.ROBOT_ACCOUNTS' | translate}}</a>
|
||||
</li>
|
||||
<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="isSessionValid && (hasConfigurationListPermission)">
|
||||
<a class="nav-link" routerLink="configs" routerLinkActive="active">{{'PROJECT_DETAIL.CONFIG' | translate}}</a>
|
||||
</li>
|
||||
|
@ -43,6 +43,7 @@ export class ProjectDetailComponent implements OnInit {
|
||||
hasLogListPermission: boolean;
|
||||
hasConfigurationListPermission: boolean;
|
||||
hasRobotListPermission: boolean;
|
||||
hasTagRetentionPermission: boolean;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@ -83,10 +84,12 @@ export class ProjectDetailComponent implements OnInit {
|
||||
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.LIST));
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.CREATE));
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.TAG_RETENTION.KEY, USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ));
|
||||
forkJoin(...permissionsList).subscribe(Rules => {
|
||||
[this.hasProjectReadPermission, this.hasLogListPermission, this.hasConfigurationListPermission, this.hasMemberListPermission
|
||||
, this.hasLabelListPermission, this.hasRepositoryListPermission, this.hasHelmChartsListPermission, this.hasRobotListPermission
|
||||
, this.hasLabelCreatePermission] = Rules;
|
||||
, this.hasLabelCreatePermission, this.hasTagRetentionPermission] = Rules;
|
||||
|
||||
}, error => this.errorHandler.error(error));
|
||||
}
|
||||
|
@ -40,6 +40,10 @@ import { HelmChartModule } from './helm-chart/helm-chart.module';
|
||||
import { RobotAccountComponent } from './robot-account/robot-account.component';
|
||||
import { AddRobotComponent } from './robot-account/add-robot/add-robot.component';
|
||||
import { AddHttpAuthGroupComponent } from './member/add-http-auth-group/add-http-auth-group.component';
|
||||
import { TagRetentionComponent } from "./tag-retention/tag-retention.component";
|
||||
import { AddRuleComponent } from "./tag-retention/add-rule/add-rule.component";
|
||||
import { TagRetentionService } from "./tag-retention/tag-retention.service";
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -63,10 +67,12 @@ import { AddHttpAuthGroupComponent } from './member/add-http-auth-group/add-http
|
||||
AddGroupComponent,
|
||||
RobotAccountComponent,
|
||||
AddRobotComponent,
|
||||
AddHttpAuthGroupComponent
|
||||
AddHttpAuthGroupComponent,
|
||||
TagRetentionComponent,
|
||||
AddRuleComponent,
|
||||
],
|
||||
exports: [ProjectComponent, ListProjectComponent],
|
||||
providers: [ProjectRoutingResolver, MemberService, RobotService]
|
||||
providers: [ProjectRoutingResolver, MemberService, RobotService, TagRetentionService]
|
||||
})
|
||||
export class ProjectModule {
|
||||
|
||||
|
@ -51,6 +51,7 @@ export class Project {
|
||||
prevent_vul: string | boolean;
|
||||
severity: string;
|
||||
auto_scan: string | boolean;
|
||||
retention_id: number;
|
||||
};
|
||||
constructor () {
|
||||
this.metadata = <any>{};
|
||||
|
@ -0,0 +1,118 @@
|
||||
<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">
|
||||
<p class="color-97">{{'TAG_RETENTION.ADD_SUBTITLE' | translate}}</p>
|
||||
<div class="height-72">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4">
|
||||
<div class="over-line"></div>
|
||||
<label>{{'TAG_RETENTION.BY_WHAT' | translate}}</label>
|
||||
</div>
|
||||
<div class="clr-col-5">
|
||||
<div class="over-line"></div>
|
||||
<div class="clr-select-wrapper w-100">
|
||||
<select [(ngModel)]="template" class="clr-select w-100">
|
||||
<option *ngFor="let t of metadata?.templates"
|
||||
value="{{t?.rule_template}}">{{getI18nKey(t?.display_text)|translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-col-3">
|
||||
<div class="over-line">
|
||||
<span *ngIf="template !=='always'">{{getI18nKey(unit)|translate}}</span>
|
||||
</div>
|
||||
<div class="w-100 disabled">
|
||||
<input *ngIf="template !=='always'" [(ngModel)]="num" class="clr-input w-100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="height-72">
|
||||
<div class="clr-row">
|
||||
<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 [(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 [(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 [(ngModel)]="tagsSelect" class="clr-select w-100">
|
||||
<option *ngFor="let d of metadata?.tag_selectors[1]?.decorations"
|
||||
value="{{d}}">{{getI18nKey(d)|translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-col-5">
|
||||
<div class="w-100">
|
||||
<input required [(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 class="height-72">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4">
|
||||
<label>{{'TAG_RETENTION.LABELS' | translate}}</label>
|
||||
</div>
|
||||
<div class="clr-col-3">
|
||||
<div class="clr-select-wrapper w-100">
|
||||
<select [(ngModel)]="labelsSelect" 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 [(ngModel)]="labelsInput" 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_LABELS' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button [disabled]="canNotAdd()" type="button" 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,13 @@
|
||||
.color-97 {
|
||||
color: #979797;
|
||||
}
|
||||
|
||||
.over-line {
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.height-72 {
|
||||
height: 72px;
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Output,
|
||||
EventEmitter,
|
||||
} from "@angular/core";
|
||||
import { Rule, RuleMetadate } from "../retention";
|
||||
import { compareValue } from "@harbor/ui";
|
||||
import { TagRetentionService } from "../tag-retention.service";
|
||||
|
||||
@Component({
|
||||
selector: "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<Rule>();
|
||||
metadata: RuleMetadate = new RuleMetadate();
|
||||
rule: Rule = new Rule();
|
||||
isAdd: boolean = true;
|
||||
editRuleOrigin: Rule;
|
||||
|
||||
constructor(private tagRetentionService: TagRetentionService) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
}
|
||||
|
||||
set template(template) {
|
||||
this.rule.template = template;
|
||||
}
|
||||
|
||||
get template() {
|
||||
return this.rule.template;
|
||||
}
|
||||
|
||||
get unit(): string {
|
||||
let str = "";
|
||||
this.metadata.templates.forEach(t => {
|
||||
if (t.rule_template === this.rule.template) {
|
||||
str = t.params[0].unit;
|
||||
}
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
get num() {
|
||||
return this.rule.params[this.template];
|
||||
}
|
||||
|
||||
set num(num) {
|
||||
this.rule.params[this.template] = num;
|
||||
}
|
||||
|
||||
get repoSelect() {
|
||||
return this.rule.scope_selectors.repository[0].decoration;
|
||||
}
|
||||
|
||||
set repoSelect(repoSelect) {
|
||||
this.rule.scope_selectors.repository[0].decoration = repoSelect;
|
||||
}
|
||||
|
||||
set repositories(repositories) {
|
||||
if (repositories.indexOf(",") !== -1) {
|
||||
this.rule.scope_selectors.repository[0].pattern = "{" + repositories + "}";
|
||||
} else {
|
||||
this.rule.scope_selectors.repository[0].pattern = repositories;
|
||||
}
|
||||
}
|
||||
|
||||
get repositories() {
|
||||
return this.rule.scope_selectors.repository[0].pattern.replace(/[{}]/g, "");
|
||||
}
|
||||
|
||||
get tagsSelect() {
|
||||
return this.rule.tag_selectors[0].decoration;
|
||||
}
|
||||
|
||||
set tagsSelect(tagsSelect) {
|
||||
this.rule.tag_selectors[0].decoration = tagsSelect;
|
||||
}
|
||||
|
||||
set tagsInput(tagsInput) {
|
||||
if (tagsInput.indexOf(",") !== -1) {
|
||||
this.rule.tag_selectors[0].pattern = "{" + tagsInput + "}";
|
||||
} else {
|
||||
this.rule.tag_selectors[0].pattern = tagsInput;
|
||||
}
|
||||
}
|
||||
|
||||
get tagsInput() {
|
||||
return this.rule.tag_selectors[0].pattern.replace(/[{}]/g, "");
|
||||
}
|
||||
|
||||
get labelsSelect() {
|
||||
return this.rule.tag_selectors[1].decoration;
|
||||
}
|
||||
|
||||
set labelsSelect(labelsSelect) {
|
||||
this.rule.tag_selectors[1].decoration = labelsSelect;
|
||||
}
|
||||
|
||||
set labelsInput(labelsInput) {
|
||||
this.rule.tag_selectors[1].pattern = labelsInput;
|
||||
}
|
||||
|
||||
get labelsInput() {
|
||||
return this.rule.tag_selectors[1].pattern;
|
||||
}
|
||||
|
||||
canNotAdd(): boolean {
|
||||
if (this.rule.template === 'always'
|
||||
&& this.rule.scope_selectors.repository[0].pattern
|
||||
&& this.rule.tag_selectors[0].pattern) {
|
||||
return false;
|
||||
}
|
||||
if (this.isAdd) {
|
||||
if (this.rule.template
|
||||
&& this.rule.params[this.template]
|
||||
&& this.rule.scope_selectors.repository[0].pattern
|
||||
&& this.rule.tag_selectors[0].pattern) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (this.rule.template
|
||||
&& this.rule.params[this.template]
|
||||
&& this.rule.scope_selectors.repository[0].pattern
|
||||
&& this.rule.tag_selectors[0].pattern && !compareValue(this.editRuleOrigin, this.rule)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
open() {
|
||||
this.addRuleOpened = true;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.addRuleOpened = false;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
add() {
|
||||
this.close();
|
||||
this.clickAdd.emit(this.rule);
|
||||
}
|
||||
|
||||
getI18nKey(str: string) {
|
||||
return this.tagRetentionService.getI18nKey(str);
|
||||
}
|
||||
}
|
133
src/portal/src/app/project/tag-retention/retention.ts
Normal file
133
src/portal/src/app/project/tag-retention/retention.ts
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
export class Retention {
|
||||
algorithm: string;
|
||||
rules: Array<Rule>;
|
||||
trigger: {
|
||||
kind: string;
|
||||
settings: {
|
||||
cron: string;
|
||||
}
|
||||
};
|
||||
scope: {
|
||||
level: string,
|
||||
ref: number;
|
||||
};
|
||||
cap: number;
|
||||
|
||||
constructor() {
|
||||
this.rules = [];
|
||||
this.algorithm = "or";
|
||||
this.trigger = {
|
||||
kind: "Schedule",
|
||||
settings: {
|
||||
cron: "@daily",
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class Rule {
|
||||
isDisabled: boolean;
|
||||
id: number;
|
||||
priority: number;
|
||||
action: string;
|
||||
template: string;
|
||||
params: object;
|
||||
tag_selectors: Array<Selector>;
|
||||
scope_selectors: {
|
||||
repository: Array<Selector>;
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.action = "retain";
|
||||
this.params = {};
|
||||
this.scope_selectors = {
|
||||
repository: [
|
||||
{
|
||||
kind: 'doublestar',
|
||||
decoration: 'repoMatches',
|
||||
pattern: '**'
|
||||
}
|
||||
]
|
||||
};
|
||||
this.tag_selectors = [
|
||||
{
|
||||
kind: 'doublestar',
|
||||
decoration: 'matches',
|
||||
pattern: '**'
|
||||
},
|
||||
{
|
||||
kind: 'label',
|
||||
decoration: "withLabels",
|
||||
pattern: null
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export class Selector {
|
||||
kind: string;
|
||||
decoration: string;
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
export class Param {
|
||||
type: string;
|
||||
unit: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export class Template {
|
||||
rule_template: string;
|
||||
display_text: string;
|
||||
action: "retain";
|
||||
params: Array<Param>;
|
||||
}
|
||||
|
||||
export class SelectorRuleMetadate {
|
||||
display_text: string;
|
||||
kind: string;
|
||||
decorations: Array<string>;
|
||||
}
|
||||
|
||||
export class RuleMetadate {
|
||||
templates: Array<Template>;
|
||||
scope_selectors: Array<SelectorRuleMetadate>;
|
||||
tag_selectors: Array<SelectorRuleMetadate>;
|
||||
|
||||
constructor() {
|
||||
this.templates = [];
|
||||
this.scope_selectors = [
|
||||
{
|
||||
display_text: null,
|
||||
kind: null,
|
||||
decorations: []
|
||||
}
|
||||
];
|
||||
this.tag_selectors = [
|
||||
{
|
||||
display_text: null,
|
||||
kind: null,
|
||||
decorations: []
|
||||
},
|
||||
{
|
||||
display_text: null,
|
||||
kind: null,
|
||||
decorations: []
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,180 @@
|
||||
<div class="clr-row pt-1 fw8">
|
||||
<div class="clr-col-2">
|
||||
<label class="fw9-l">{{'TAG_RETENTION.RETENTION_RULES' | translate}}</label><span
|
||||
class="badge badge-3 clr-offset-1">{{retention?.rules?.length ? retention?.rules?.length : 0}}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="clr-row pt-1">
|
||||
<div class="clr-col">
|
||||
<ul *ngIf="retention?.rules?.length > 0" class="list-unstyled">
|
||||
<li class="rule" *ngFor="let rule of retention?.rules;let i = index;">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col rule-name">
|
||||
<span>
|
||||
<span>{{getI18nKey(rule?.action)|translate}}</span>
|
||||
<span>{{getI18nKey(rule?.template)|translate:{number: rule?.params[rule?.template] } }}</span>
|
||||
<span class="color-97">{{'TAG_RETENTION.WITH_CONDITION' | translate}}</span>
|
||||
<span>{{'TAG_RETENTION.REPO' | translate}}</span>
|
||||
<span>{{getI18nKey(rule?.scope_selectors?.repository[0]?.decoration)|translate}}</span>
|
||||
<span>{{formatPattern(rule?.scope_selectors?.repository[0]?.pattern)}}</span>
|
||||
<span class="color-97 space">{{'TAG_RETENTION.AND' | translate}}</span>
|
||||
<span>{{'TAG_RETENTION.LOWER_TAGS' | translate}}</span>
|
||||
<span>{{getI18nKey(rule?.tag_selectors[0]?.decoration)|translate}}</span>
|
||||
<span>{{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 class="clr-col-1">
|
||||
<clr-icon (click)="openEditor(i)" shape="ellipsis-vertical"
|
||||
class="dropdown-toggle hand"></clr-icon>
|
||||
<div class="dropdown" [ngClass]="{open:ruleIndex===i}">
|
||||
<div class="dropdown-menu">
|
||||
<button type="button" class="dropdown-item"
|
||||
(click)="editRuleByIndex(i)">{{'TAG_RETENTION.EDIT' | translate}}</button>
|
||||
<button type="button" class="dropdown-item"
|
||||
(click)="deleteRule(i)">{{'TAG_RETENTION.DELETE' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clr-row pt-1">
|
||||
<div class="clr-col-5 color-97">
|
||||
<div>
|
||||
<span>{{'TAG_RETENTION.ADD_RULE_HELP_1' | translate}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{'TAG_RETENTION.ADD_RULE_HELP_2' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-col">
|
||||
<button class="btn btn-link" (click)="openAddRule()">{{'TAG_RETENTION.ADD_RULE' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row pt-1">
|
||||
<div class="clr-col-2 pt-2"><label class="fw9-l">{{'TAG_RETENTION.RETENTION_RUNS' | translate}}</label></div>
|
||||
<div class="clr-col-10">
|
||||
<clr-dg-action-bar>
|
||||
<button [disabled]="!(retention?.rules?.length > 0)" class="btn btn-outline"
|
||||
(click)="isRetentionRunOpened=true">
|
||||
<clr-icon shape="play"></clr-icon>
|
||||
<span class="ml-5">{{'TAG_RETENTION.RUN_NOW' | translate}}</span></button>
|
||||
<button [disabled]="!(retention?.rules?.length > 0)" class="btn btn-outline"
|
||||
(click)="whatIfRun()">{{'TAG_RETENTION.WHAT_IF_RUN' | translate}}</button>
|
||||
<button [disabled]="!(selectedItem && (selectedItem.status ==='InProgress' || selectedItem.status ==='Running'))"
|
||||
class="btn btn-outline" (click)="abortRun()">{{'TAG_RETENTION.ABORT' | translate}}</button>
|
||||
<button [disabled]="!retentionId" class="btn btn-outline"
|
||||
(click)="refreshList()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-datagrid [clrDgLoading]="loadingExecutions" [(clrDgSingleSelected)]="selectedItem">
|
||||
<clr-dg-column>
|
||||
{{'TAG_RETENTION.SERIAL' | translate}}
|
||||
<clr-dg-string-filter [clrDgStringFilter]="serialFilter"></clr-dg-string-filter>
|
||||
</clr-dg-column>
|
||||
<clr-dg-column>
|
||||
{{'TAG_RETENTION.STATUS' | translate}}
|
||||
<clr-dg-string-filter [clrDgStringFilter]="statusFilter"></clr-dg-string-filter>
|
||||
</clr-dg-column>
|
||||
<clr-dg-column>
|
||||
{{'TAG_RETENTION.DRY_RUN' | translate}}
|
||||
<clr-dg-string-filter [clrDgStringFilter]="dryRunFilter"></clr-dg-string-filter>
|
||||
</clr-dg-column>
|
||||
<clr-dg-column>
|
||||
{{'TAG_RETENTION.START_TIME' | translate}}
|
||||
</clr-dg-column>
|
||||
<clr-dg-column>
|
||||
{{'TAG_RETENTION.DURATION' | translate}}
|
||||
</clr-dg-column>
|
||||
<clr-dg-placeholder>
|
||||
{{'TAG_RETENTION.NO_EXECUTION' | translate}}
|
||||
</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let execution of executionList;let i = index;" [clrDgItem]="execution">
|
||||
<clr-dg-cell class="hand" (click)="openDetail(i,execution.id)">
|
||||
<clr-icon shape="angle" [dir]="index===i?'down':'right'"></clr-icon>
|
||||
<span class="ml-1">{{execution.id}}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell class="hand" (click)="openDetail(i,execution.id)">{{execution.status}}</clr-dg-cell>
|
||||
<clr-dg-cell class="hand"
|
||||
(click)="openDetail(i,execution.id)">{{execution.dry_run ? 'YES' : 'NO'}}</clr-dg-cell>
|
||||
<clr-dg-cell class="hand"
|
||||
(click)="openDetail(i,execution.id)">{{execution.start_time|date:'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell class="hand" (click)="openDetail(i,execution.id)">{{execution.duration}}</clr-dg-cell>
|
||||
<clr-dg-row-detail *ngIf="index===i">
|
||||
<clr-datagrid [clrDgLoading]="loadingHistories" class="w-100">
|
||||
<clr-dg-column>{{'TAG_RETENTION.REPOSITORY' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'TAG_RETENTION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'TAG_RETENTION.START_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'TAG_RETENTION.DURATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'TAG_RETENTION.LOG' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>
|
||||
{{'TAG_RETENTION.NO_HISTORY' | translate}}
|
||||
</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let task of historyList" [clrDgItem]="task">
|
||||
<clr-dg-cell>{{task.repository}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{task.status}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{task.start_time|date:'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{task.duration}}</clr-dg-cell>
|
||||
<clr-dg-cell><span (click)="seeLog(task.execution_id,task.id)"
|
||||
class="hand color-79b">{{'TAG_RETENTION.LOG' | translate}}</span>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="innerPagination.totalItems">{{innerPagination.firstItem + 1}}
|
||||
-
|
||||
{{innerPagination.lastItem + 1 }} {{'ROBOT_ACCOUNT.OF' |
|
||||
translate}} </span>
|
||||
{{innerPagination.totalItems }} {{'ROBOT_ACCOUNT.ITEMS' | translate}}
|
||||
<clr-dg-pagination #innerPagination [clrDgPageSize]="5"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</clr-dg-row-detail>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}}
|
||||
-
|
||||
{{pagination.lastItem + 1 }} {{'ROBOT_ACCOUNT.OF' |
|
||||
translate}} </span>
|
||||
{{pagination.totalItems }} {{'ROBOT_ACCOUNT.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
<add-rule #addRule (clickAdd)="clickAdd($event)"></add-rule>
|
||||
<clr-modal [(clrModalOpen)]="isRetentionRunOpened"
|
||||
[clrModalStaticBackdrop]="true" [clrModalClosable]="true">
|
||||
<h3 class="modal-title">{{'TAG_RETENTION.RETENTION_RUN' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<p class="color-97">
|
||||
{{'TAG_RETENTION.RETENTION_RUN_EXPLAIN' | translate}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline"
|
||||
(click)="isRetentionRunOpened=false">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="runRetention()">{{'BUTTON.RUN' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<clr-modal [(clrModalOpen)]="isAbortedOpened"
|
||||
[clrModalStaticBackdrop]="true" [clrModalClosable]="true">
|
||||
<h3 class="modal-title">{{'TAG_RETENTION.RETENTION_RUN_ABORTED' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<p class="color-97">{{'TAG_RETENTION.RETENTION_RUN_ABORTED_EXPLAIN' | translate}}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary"
|
||||
(click)="abortRetention()">{{'BUTTON.OK' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<div class="backdrop-transparent" (click)="ruleIndex = -1" *ngIf="ruleIndex !== -1"></div>
|
@ -0,0 +1,46 @@
|
||||
.color-97 {
|
||||
color: #979797;
|
||||
}
|
||||
|
||||
.rule {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.rule-name {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ml-5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.width-60 {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.hand {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.datagrid-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.color-79b {
|
||||
color: #0079b8;
|
||||
}
|
||||
|
||||
.backdrop-transparent {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
z-index: 999;
|
||||
}
|
@ -0,0 +1,290 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AddRuleComponent } from "./add-rule/add-rule.component";
|
||||
import { ClrDatagridStringFilterInterface } from "@clr/angular";
|
||||
import { TagRetentionService } from "./tag-retention.service";
|
||||
import { Retention, Rule } from "./retention";
|
||||
import { Project } from "../project";
|
||||
import { clone, ErrorHandler } from "@harbor/ui";
|
||||
|
||||
const MIN = 60000;
|
||||
const SEC = 1000;
|
||||
const MIN_STR = "min";
|
||||
const SEC_STR = "sec";
|
||||
|
||||
@Component({
|
||||
selector: 'tag-retention',
|
||||
templateUrl: './tag-retention.component.html',
|
||||
styleUrls: ['./tag-retention.component.scss']
|
||||
})
|
||||
export class TagRetentionComponent implements OnInit {
|
||||
serialFilter: ClrDatagridStringFilterInterface<any> = {
|
||||
accepts(item: any, search: string): boolean {
|
||||
return item.id.toString().indexOf(search) !== -1;
|
||||
}
|
||||
};
|
||||
statusFilter: ClrDatagridStringFilterInterface<any> = {
|
||||
accepts(item: any, search: string): boolean {
|
||||
return item.status.toLowerCase().indexOf(search.toLowerCase()) !== -1;
|
||||
}
|
||||
};
|
||||
dryRunFilter: ClrDatagridStringFilterInterface<any> = {
|
||||
accepts(item: any, search: string): boolean {
|
||||
let str = item.dry_run ? 'YES' : 'NO';
|
||||
return str.indexOf(search) !== -1;
|
||||
}
|
||||
};
|
||||
projectId: number;
|
||||
addRuleOpened: boolean = false;
|
||||
isRetentionRunOpened: boolean = false;
|
||||
isAbortedOpened: boolean = false;
|
||||
selectedItem: any;
|
||||
ruleIndex: number = -1;
|
||||
index: number = -1;
|
||||
retentionId: number;
|
||||
retention: Retention = new Retention();
|
||||
editIndex: number;
|
||||
executionList = [];
|
||||
historyList = [];
|
||||
loadingExecutions: boolean = false;
|
||||
loadingHistories: boolean = false;
|
||||
dryRun: boolean = false;
|
||||
@ViewChild('addRule') addRuleComponent: AddRuleComponent;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private tagRetentionService: TagRetentionService,
|
||||
private errorHandler: ErrorHandler,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
this.retention.scope = {
|
||||
level: "project",
|
||||
ref: this.projectId
|
||||
};
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
if (resolverData) {
|
||||
let project = <Project>resolverData["projectResolver"];
|
||||
if (project.metadata && project.metadata.retention_id) {
|
||||
this.retentionId = project.metadata.retention_id;
|
||||
}
|
||||
}
|
||||
this.getRetention();
|
||||
this.getMetadata();
|
||||
this.refreshList();
|
||||
}
|
||||
|
||||
getMetadata() {
|
||||
this.tagRetentionService.getRetentionMetadata().subscribe(
|
||||
response => {
|
||||
this.addRuleComponent.metadata = response;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
getRetention() {
|
||||
if (this.retentionId) {
|
||||
this.tagRetentionService.getRetention(this.retentionId).subscribe(
|
||||
response => {
|
||||
this.retention = response;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
editRuleByIndex(index) {
|
||||
this.editIndex = index;
|
||||
this.addRuleComponent.rule = clone(this.retention.rules[index]);
|
||||
this.addRuleComponent.editRuleOrigin = clone(this.retention.rules[index]);
|
||||
this.addRuleComponent.open();
|
||||
this.addRuleComponent.isAdd = false;
|
||||
this.ruleIndex = -1;
|
||||
}
|
||||
|
||||
deleteRule(index) {
|
||||
let retention: Retention = clone(this.retention);
|
||||
retention.rules.splice(index, 1);
|
||||
this.ruleIndex = -1;
|
||||
this.tagRetentionService.updateRetention(this.retentionId, retention).subscribe(
|
||||
response => {
|
||||
this.retention = retention;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
openAddRule() {
|
||||
this.addRuleComponent.open();
|
||||
this.addRuleComponent.isAdd = true;
|
||||
this.addRuleComponent.rule = new Rule();
|
||||
}
|
||||
|
||||
runRetention() {
|
||||
this.isRetentionRunOpened = false;
|
||||
this.tagRetentionService.runNowTrigger(this.retentionId).subscribe(
|
||||
response => {
|
||||
this.refreshList();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
whatIfRun() {
|
||||
this.tagRetentionService.whatIfRunTrigger(this.retentionId).subscribe(
|
||||
response => {
|
||||
this.refreshList();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
refreshList() {
|
||||
this.index = -1 ;
|
||||
if (this.retentionId) {
|
||||
this.loadingExecutions = true;
|
||||
this.tagRetentionService.getRunNowList(this.retentionId).subscribe(
|
||||
res => {
|
||||
this.loadingExecutions = false;
|
||||
this.executionList = res;
|
||||
TagRetentionComponent.calculateDuration(this.executionList);
|
||||
}, error => {
|
||||
this.loadingExecutions = false;
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static calculateDuration(arr: Array<any>) {
|
||||
if (arr && arr.length > 0) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let duration = new Date(arr[i].end_time).getTime() - new Date(arr[i].start_time).getTime();
|
||||
let min = Math.floor(duration / MIN);
|
||||
let sec = Math.floor((duration % MIN) / SEC);
|
||||
arr[i]['duration'] = "";
|
||||
if ((min || sec) && duration > 0) {
|
||||
if (min) {
|
||||
arr[i]['duration'] += '' + min + MIN_STR;
|
||||
}
|
||||
if (sec) {
|
||||
arr[i]['duration'] += '' + sec + SEC_STR;
|
||||
}
|
||||
} else if ( min === 0 && sec === 0 && duration > 0) {
|
||||
arr[i]['duration'] = "0";
|
||||
} else {
|
||||
arr[i]['duration'] = "N/A";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abortRun() {
|
||||
this.isAbortedOpened = true;
|
||||
this.tagRetentionService.AbortRun(this.retentionId, this.selectedItem.id).subscribe(
|
||||
res => {
|
||||
this.refreshList();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
abortRetention() {
|
||||
this.isAbortedOpened = false;
|
||||
}
|
||||
|
||||
openEditor(index) {
|
||||
if (this.ruleIndex !== index) {
|
||||
this.ruleIndex = index;
|
||||
} else {
|
||||
this.ruleIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
openDetail(index, executionId) {
|
||||
if (this.index !== index) {
|
||||
this.index = index;
|
||||
this.historyList = [];
|
||||
this.loadingHistories = true;
|
||||
this.tagRetentionService.getExecutionHistory(this.retentionId, executionId).subscribe(
|
||||
res => {
|
||||
this.loadingHistories = false;
|
||||
this.historyList = res;
|
||||
TagRetentionComponent.calculateDuration(this.historyList);
|
||||
}, error => {
|
||||
this.loadingHistories = false;
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
} else {
|
||||
this.index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
refreshAfterCreatRetention() {
|
||||
this.tagRetentionService.getProjectInfo(this.projectId).subscribe(
|
||||
response => {
|
||||
this.retentionId = response.metadata.retention_id;
|
||||
this.getRetention();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
clickAdd(rule) {
|
||||
if (this.addRuleComponent.isAdd) {
|
||||
let retention: Retention = clone(this.retention);
|
||||
retention.rules.push(rule);
|
||||
if (!this.retentionId) {
|
||||
this.tagRetentionService.createRetention(retention).subscribe(
|
||||
response => {
|
||||
this.refreshAfterCreatRetention();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
} else {
|
||||
this.tagRetentionService.updateRetention(this.retentionId, retention).subscribe(
|
||||
response => {
|
||||
this.retention = retention;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let retention: Retention = clone(this.retention);
|
||||
retention.rules[this.editIndex] = rule;
|
||||
this.tagRetentionService.updateRetention(this.retentionId, retention).subscribe(
|
||||
response => {
|
||||
this.retention = retention;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
seeLog(executionId, taskId) {
|
||||
this.tagRetentionService.seeLog(this.retentionId, executionId, taskId);
|
||||
}
|
||||
|
||||
formatPattern(pattern: string): string {
|
||||
return pattern.replace(/[{}]/g, "");
|
||||
}
|
||||
|
||||
getI18nKey(str: string) {
|
||||
return this.tagRetentionService.getI18nKey(str);
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Injectable } from "@angular/core";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Retention, RuleMetadate } from "./retention";
|
||||
import { Observable, throwError as observableThrowError } from "rxjs";
|
||||
import { map, catchError } from "rxjs/operators";
|
||||
import { Project } from "../project";
|
||||
|
||||
@Injectable()
|
||||
export class TagRetentionService {
|
||||
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",
|
||||
"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",
|
||||
"repoMatches": "MAT",
|
||||
"repoExcludes": "EXC",
|
||||
"matches": "MAT",
|
||||
"excludes": "EXC",
|
||||
"withLabels": "WITH",
|
||||
"withoutLabels": "WITHOUT",
|
||||
"COUNT": "UNIT_COUNT",
|
||||
"DAYS": "UNIT_DAY",
|
||||
};
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
getRetention(retentionId): Observable<Retention> {
|
||||
return this.http.get(`/api/retentions/${retentionId}`)
|
||||
.pipe(map(response => response as Retention))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
createRetention(retention: Retention) {
|
||||
return this.http.post(`/api/retentions`, retention)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
updateRetention(retentionId, retention: Retention) {
|
||||
return this.http.put(`/api/retentions/${retentionId}`, retention)
|
||||
.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)));
|
||||
}
|
||||
|
||||
runNowTrigger(retentionId) {
|
||||
return this.http.post(`/api/retentions/${retentionId}/executions`, {dry_run: false})
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
whatIfRunTrigger(retentionId) {
|
||||
return this.http.post(`/api/retentions/${retentionId}/executions`, {dry_run: true})
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
AbortRun(retentionId, executionId) {
|
||||
return this.http.patch(`/api/retentions/${retentionId}/executions/${executionId}`, {action: 'stop'})
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
getRunNowList(retentionId) {
|
||||
return this.http.get(`/api/retentions/${retentionId}/executions`)
|
||||
.pipe(map(response => response as Array<any>))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
getExecutionHistory(retentionId, executionId) {
|
||||
return this.http.get(`/api/retentions/${retentionId}/executions/${executionId}/tasks`)
|
||||
.pipe(map(response => response as Array<any>))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
seeLog(retentionId, executionId, taskId) {
|
||||
window.open(`api/retentions/${retentionId}/executions/${executionId}/tasks/${taskId}`, '_blank');
|
||||
}
|
||||
}
|
@ -47,9 +47,10 @@ export class UserService {
|
||||
, catchError(error => this.handleError(error)));
|
||||
}
|
||||
getUsers(): Observable<User[]> {
|
||||
return this.http.get(userMgmtEndpoint, HTTP_GET_OPTIONS)
|
||||
.pipe(map(response => response as User[])
|
||||
, catchError(error => this.handleError(error)));
|
||||
return this.http.get(userMgmtEndpoint)
|
||||
.pipe(map(((response: any) => {
|
||||
return response as User[];
|
||||
}), catchError(error => this.handleError(error))));
|
||||
}
|
||||
|
||||
// Add new user
|
||||
|
@ -43,7 +43,9 @@
|
||||
"ACTIONS": "Actions",
|
||||
"BROWSE": "Browse",
|
||||
"UPLOAD": "Upload",
|
||||
"NO_FILE": "No file selected"
|
||||
"NO_FILE": "No file selected",
|
||||
"ADD": "ADD",
|
||||
"RUN": "RUN"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "Deleted successfully",
|
||||
@ -1090,6 +1092,76 @@
|
||||
"SYS_WHITELIST": "System whitelist",
|
||||
"PRO_WHITELIST": "Project whitelist",
|
||||
"ADD_SYSTEM": "ADD SYSTEM"
|
||||
},
|
||||
"TAG_RETENTION": {
|
||||
"TAG_RETENTION": "Tag Retention Policy",
|
||||
"RETENTION_RULES": "Retention rules",
|
||||
"RULE_NAME_1": " the images from the last {{number}} days",
|
||||
"RULE_NAME_2": " the most recent active {{number}} images",
|
||||
"RULE_NAME_3": " the most recently pushed {{number}} images",
|
||||
"RULE_NAME_4": " the most recently pulled {{number}} images",
|
||||
"RULE_NAME_5": " always",
|
||||
"ADD_RULE": "ADD RULE",
|
||||
"ADD_RULE_HELP_1": "Click the ADD RULE button to add a rule.",
|
||||
"ADD_RULE_HELP_2": "Tag retention polices run once a day.",
|
||||
"RETENTION_RUNS": "Retention runs",
|
||||
"RUN_NOW": "RUN NOW",
|
||||
"WHAT_IF_RUN": "DRY RUN",
|
||||
"ABORT": "ABORT",
|
||||
"SERIAL": "ID",
|
||||
"STATUS": "Status",
|
||||
"DRY_RUN": "Dry Run",
|
||||
"START_TIME": "Start Time",
|
||||
"DURATION": "Duration",
|
||||
"DETAILS": "Details",
|
||||
"REPOSITORY": "Repository",
|
||||
"EDIT": "Edit",
|
||||
"DISABLE": "Disable",
|
||||
"ENABLE": "Enable",
|
||||
"DELETE": "Delete",
|
||||
"ADD_TITLE": "Add Tag Retention Rule",
|
||||
"ADD_SUBTITLE": "Specify a tag retention rule for this project. All tag retention rules are independently calculated and each rule can be applied to a selected list of repositories.",
|
||||
"BY_WHAT": "By image count or number of days",
|
||||
"RULE_TEMPLATE_1": "the images from the last # days",
|
||||
"RULE_TEMPLATE_2": "the most recent active # images",
|
||||
"RULE_TEMPLATE_3": "the most recently pushed # images",
|
||||
"RULE_TEMPLATE_4": "the most recently pulled # images",
|
||||
"RULE_TEMPLATE_5": "always",
|
||||
"ACTION_RETAIN": "Retain",
|
||||
"UNIT_DAY": "DAYS",
|
||||
"UNIT_COUNT": "COUNT",
|
||||
"NUMBER": "NUMBER",
|
||||
"IN_REPOSITORIES": "Repositories",
|
||||
"REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **",
|
||||
"TAGS": "Tags",
|
||||
"MATCHES_TAGS": "Matches tags",
|
||||
"MATCHES_EXCEPT_TAGS": "Matches except tags",
|
||||
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,**,or regex",
|
||||
"LABELS": "Labels",
|
||||
"MATCHES_LABELS": "Matches Labels",
|
||||
"MATCHES_EXCEPT_LABELS": "Matches except Labels",
|
||||
"REP_LABELS": "Enter multiple comma separated labels",
|
||||
"RETENTION_RUN": "Retention Run",
|
||||
"RETENTION_RUN_EXPLAIN": "Executing the retention policy can have adverse effects to the images in this project and affected image tags will be deleted. Press CANCEL and use a DRY RUN to simulate the effect of this policy. Otherwise press RUN to proceed.",
|
||||
"RETENTION_RUN_ABORTED": "Retention Run Aborted",
|
||||
"RETENTION_RUN_ABORTED_EXPLAIN": "This retention run has been aborted. Images already deleted are irreversible. You can initiate another run to continue to delete images. In order to simulate a run, you can use the “DRY RUN”.",
|
||||
"LOADING": "Loading...",
|
||||
"NO_EXECUTION": "We couldn't find any executions!",
|
||||
"NO_HISTORY": "We couldn't find any histories!",
|
||||
"DELETION": "Deletions",
|
||||
"EDIT_TITLE": "Edit Tag Retention Rule",
|
||||
"LOG": "Log",
|
||||
"EXCLUDES": "Excludes",
|
||||
"MATCHES": "Matches",
|
||||
"REPO": " repositories",
|
||||
"EXC": " excluding ",
|
||||
"MAT": " matching ",
|
||||
"AND": " and",
|
||||
"WITH": " with ",
|
||||
"WITHOUT": " without ",
|
||||
"LOWER_LABELS": " labels",
|
||||
"WITH_CONDITION": " with",
|
||||
"LOWER_TAGS": " tags"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,7 +43,9 @@
|
||||
"ACTIONS": "Actions",
|
||||
"BROWSE": "Browse",
|
||||
"UPLOAD": "Upload",
|
||||
"NO_FILE": "No file selected"
|
||||
"NO_FILE": "No file selected",
|
||||
"ADD": "ADD",
|
||||
"RUN": "RUN"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "Deleted successfully",
|
||||
@ -1088,6 +1090,76 @@
|
||||
"SYS_WHITELIST": "System whitelist",
|
||||
"PRO_WHITELIST": "Project whitelist",
|
||||
"ADD_SYSTEM": "ADD SYSTEM"
|
||||
},
|
||||
"TAG_RETENTION": {
|
||||
"TAG_RETENTION": "Tag Retention Policy",
|
||||
"RETENTION_RULES": "Retention rules",
|
||||
"RULE_NAME_1": " the images from the last {{number}} days",
|
||||
"RULE_NAME_2": " the most recent active {{number}} images",
|
||||
"RULE_NAME_3": " the most recently pushed {{number}} images",
|
||||
"RULE_NAME_4": " the most recently pulled {{number}} images",
|
||||
"RULE_NAME_5": " always",
|
||||
"ADD_RULE": "ADD RULE",
|
||||
"ADD_RULE_HELP_1": "Click the ADD RULE button to add a rule.",
|
||||
"ADD_RULE_HELP_2": "Tag retention polices run once a day.",
|
||||
"RETENTION_RUNS": "Retention runs",
|
||||
"RUN_NOW": "RUN NOW",
|
||||
"WHAT_IF_RUN": "DRY RUN",
|
||||
"ABORT": "ABORT",
|
||||
"SERIAL": "ID",
|
||||
"STATUS": "Status",
|
||||
"DRY_RUN": "Dry Run",
|
||||
"START_TIME": "Start Time",
|
||||
"DURATION": "Duration",
|
||||
"DETAILS": "Details",
|
||||
"REPOSITORY": "Repository",
|
||||
"EDIT": "Edit",
|
||||
"DISABLE": "Disable",
|
||||
"ENABLE": "Enable",
|
||||
"DELETE": "Delete",
|
||||
"ADD_TITLE": "Add Tag Retention Rule",
|
||||
"ADD_SUBTITLE": "Specify a tag retention rule for this project. All tag retention rules are independently calculated and each rule can be applied to a selected list of repositories.",
|
||||
"BY_WHAT": "By image count or number of days",
|
||||
"RULE_TEMPLATE_1": "the images from the last # days",
|
||||
"RULE_TEMPLATE_2": "the most recent active # images",
|
||||
"RULE_TEMPLATE_3": "the most recently pushed # images",
|
||||
"RULE_TEMPLATE_4": "the most recently pulled # images",
|
||||
"RULE_TEMPLATE_5": "always",
|
||||
"ACTION_RETAIN": "Retain",
|
||||
"UNIT_DAY": "DAYS",
|
||||
"UNIT_COUNT": "COUNT",
|
||||
"NUMBER": "NUMBER",
|
||||
"IN_REPOSITORIES": "Repositories",
|
||||
"REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **",
|
||||
"TAGS": "Tags",
|
||||
"MATCHES_TAGS": "Matches tags",
|
||||
"MATCHES_EXCEPT_TAGS": "Matches except tags",
|
||||
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,**,or regex",
|
||||
"LABELS": "Labels",
|
||||
"MATCHES_LABELS": "Matches Labels",
|
||||
"MATCHES_EXCEPT_LABELS": "Matches except Labels",
|
||||
"REP_LABELS": "Enter multiple comma separated labels",
|
||||
"RETENTION_RUN": "Retention Run",
|
||||
"RETENTION_RUN_EXPLAIN": "Executing the retention policy can have adverse effects to the images in this project and affected image tags will be deleted. Press CANCEL and use a DRY RUN to simulate the effect of this policy. Otherwise press RUN to proceed.",
|
||||
"RETENTION_RUN_ABORTED": "Retention Run Aborted",
|
||||
"RETENTION_RUN_ABORTED_EXPLAIN": "This retention run has been aborted. Images already deleted are irreversible. You can initiate another run to continue to delete images. In order to simulate a run, you can use the “DRY RUN”.",
|
||||
"LOADING": "Loading...",
|
||||
"NO_EXECUTION": "We couldn't find any executions!",
|
||||
"NO_HISTORY": "We couldn't find any histories!",
|
||||
"DELETION": "Deletions",
|
||||
"EDIT_TITLE": "Edit Tag Retention Rule",
|
||||
"LOG": "Log",
|
||||
"EXCLUDES": "Excludes",
|
||||
"MATCHES": "Matches",
|
||||
"REPO": " repositories",
|
||||
"EXC": " excluding ",
|
||||
"MAT": " matching ",
|
||||
"AND": " and",
|
||||
"WITH": " with ",
|
||||
"WITHOUT": " without ",
|
||||
"LOWER_LABELS": " labels",
|
||||
"WITH_CONDITION": " with",
|
||||
"LOWER_TAGS": " tags"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,7 +40,9 @@
|
||||
"ACTIONS": "Actions",
|
||||
"BROWSE": "Browse",
|
||||
"UPLOAD": "Upload",
|
||||
"NO_FILE": "No file selected"
|
||||
"NO_FILE": "No file selected",
|
||||
"ADD": "ADD",
|
||||
"RUN": "RUN"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "Deleted successfully",
|
||||
@ -1060,6 +1062,76 @@
|
||||
"SYS_WHITELIST": "System whitelist",
|
||||
"PRO_WHITELIST": "Project whitelist",
|
||||
"ADD_SYSTEM": "ADD SYSTEM"
|
||||
},
|
||||
"TAG_RETENTION": {
|
||||
"TAG_RETENTION": "Tag Retention Policy",
|
||||
"RETENTION_RULES": "Retention rules",
|
||||
"RULE_NAME_1": " the images from the last {{number}} days",
|
||||
"RULE_NAME_2": " the most recent active {{number}} images",
|
||||
"RULE_NAME_3": " the most recently pushed {{number}} images",
|
||||
"RULE_NAME_4": " the most recently pulled {{number}} images",
|
||||
"RULE_NAME_5": " always",
|
||||
"ADD_RULE": "ADD RULE",
|
||||
"ADD_RULE_HELP_1": "Click the ADD RULE button to add a rule.",
|
||||
"ADD_RULE_HELP_2": "Tag retention polices run once a day.",
|
||||
"RETENTION_RUNS": "Retention runs",
|
||||
"RUN_NOW": "RUN NOW",
|
||||
"WHAT_IF_RUN": "DRY RUN",
|
||||
"ABORT": "ABORT",
|
||||
"SERIAL": "ID",
|
||||
"STATUS": "Status",
|
||||
"DRY_RUN": "Dry Run",
|
||||
"START_TIME": "Start Time",
|
||||
"DURATION": "Duration",
|
||||
"DETAILS": "Details",
|
||||
"REPOSITORY": "Repository",
|
||||
"EDIT": "Edit",
|
||||
"DISABLE": "Disable",
|
||||
"ENABLE": "Enable",
|
||||
"DELETE": "Delete",
|
||||
"ADD_TITLE": "Add Tag Retention Rule",
|
||||
"ADD_SUBTITLE": "Specify a tag retention rule for this project. All tag retention rules are independently calculated and each rule can be applied to a selected list of repositories.",
|
||||
"BY_WHAT": "By image count or number of days",
|
||||
"RULE_TEMPLATE_1": "the images from the last # days",
|
||||
"RULE_TEMPLATE_2": "the most recent active # images",
|
||||
"RULE_TEMPLATE_3": "the most recently pushed # images",
|
||||
"RULE_TEMPLATE_4": "the most recently pulled # images",
|
||||
"RULE_TEMPLATE_5": "always",
|
||||
"ACTION_RETAIN": "Retain",
|
||||
"UNIT_DAY": "DAYS",
|
||||
"UNIT_COUNT": "COUNT",
|
||||
"NUMBER": "NUMBER",
|
||||
"IN_REPOSITORIES": "Repositories",
|
||||
"REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **",
|
||||
"TAGS": "Tags",
|
||||
"MATCHES_TAGS": "Matches tags",
|
||||
"MATCHES_EXCEPT_TAGS": "Matches except tags",
|
||||
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,**,or regex",
|
||||
"LABELS": "Labels",
|
||||
"MATCHES_LABELS": "Matches Labels",
|
||||
"MATCHES_EXCEPT_LABELS": "Matches except Labels",
|
||||
"REP_LABELS": "Enter multiple comma separated labels",
|
||||
"RETENTION_RUN": "Retention Run",
|
||||
"RETENTION_RUN_EXPLAIN": "Executing the retention policy can have adverse effects to the images in this project and affected image tags will be deleted. Press CANCEL and use a DRY RUN to simulate the effect of this policy. Otherwise press RUN to proceed.",
|
||||
"RETENTION_RUN_ABORTED": "Retention Run Aborted",
|
||||
"RETENTION_RUN_ABORTED_EXPLAIN": "This retention run has been aborted. Images already deleted are irreversible. You can initiate another run to continue to delete images. In order to simulate a run, you can use the “DRY RUN”.",
|
||||
"LOADING": "Loading...",
|
||||
"NO_EXECUTION": "We couldn't find any executions!",
|
||||
"NO_HISTORY": "We couldn't find any histories!",
|
||||
"DELETION": "Deletions",
|
||||
"EDIT_TITLE": "Edit Tag Retention Rule",
|
||||
"LOG": "Log",
|
||||
"EXCLUDES": "Excludes",
|
||||
"MATCHES": "Matches",
|
||||
"REPO": " repositories",
|
||||
"EXC": " excluding ",
|
||||
"MAT": " matching ",
|
||||
"AND": " and",
|
||||
"WITH": " with ",
|
||||
"WITHOUT": " without ",
|
||||
"LOWER_LABELS": " labels",
|
||||
"WITH_CONDITION": " with",
|
||||
"LOWER_TAGS": " tags"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,7 +43,9 @@
|
||||
"ACTIONS": "Ações",
|
||||
"BROWSE": "Navegar",
|
||||
"UPLOAD": "Upload",
|
||||
"NO_FILE": "Nenhum arquivo selecionado"
|
||||
"NO_FILE": "Nenhum arquivo selecionado",
|
||||
"ADD": "ADD",
|
||||
"RUN": "RUN"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "Removido com sucesso",
|
||||
@ -1085,6 +1087,76 @@
|
||||
"SYS_WHITELIST": "System whitelist",
|
||||
"PRO_WHITELIST": "Project whitelist",
|
||||
"ADD_SYSTEM": "ADD SYSTEM"
|
||||
},
|
||||
"TAG_RETENTION": {
|
||||
"TAG_RETENTION": "Tag Retention Policy",
|
||||
"RETENTION_RULES": "Retention rules",
|
||||
"RULE_NAME_1": " the images from the last {{number}} days",
|
||||
"RULE_NAME_2": " the most recent active {{number}} images",
|
||||
"RULE_NAME_3": " the most recently pushed {{number}} images",
|
||||
"RULE_NAME_4": " the most recently pulled {{number}} images",
|
||||
"RULE_NAME_5": " always",
|
||||
"ADD_RULE": "ADD RULE",
|
||||
"ADD_RULE_HELP_1": "Click the ADD RULE button to add a rule.",
|
||||
"ADD_RULE_HELP_2": "Tag retention polices run once a day.",
|
||||
"RETENTION_RUNS": "Retention runs",
|
||||
"RUN_NOW": "RUN NOW",
|
||||
"WHAT_IF_RUN": "DRY RUN",
|
||||
"ABORT": "ABORT",
|
||||
"SERIAL": "ID",
|
||||
"STATUS": "Status",
|
||||
"DRY_RUN": "Dry Run",
|
||||
"START_TIME": "Start Time",
|
||||
"DURATION": "Duration",
|
||||
"DETAILS": "Details",
|
||||
"REPOSITORY": "Repository",
|
||||
"EDIT": "Edit",
|
||||
"DISABLE": "Disable",
|
||||
"ENABLE": "Enable",
|
||||
"DELETE": "Delete",
|
||||
"ADD_TITLE": "Add Tag Retention Rule",
|
||||
"ADD_SUBTITLE": "Specify a tag retention rule for this project. All tag retention rules are independently calculated and each rule can be applied to a selected list of repositories.",
|
||||
"BY_WHAT": "By image count or number of days",
|
||||
"RULE_TEMPLATE_1": "the images from the last # days",
|
||||
"RULE_TEMPLATE_2": "the most recent active # images",
|
||||
"RULE_TEMPLATE_3": "the most recently pushed # images",
|
||||
"RULE_TEMPLATE_4": "the most recently pulled # images",
|
||||
"RULE_TEMPLATE_5": "always",
|
||||
"ACTION_RETAIN": "Retain",
|
||||
"UNIT_DAY": "DAYS",
|
||||
"UNIT_COUNT": "COUNT",
|
||||
"NUMBER": "NUMBER",
|
||||
"IN_REPOSITORIES": "Repositories",
|
||||
"REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **",
|
||||
"TAGS": "Tags",
|
||||
"MATCHES_TAGS": "Matches tags",
|
||||
"MATCHES_EXCEPT_TAGS": "Matches except tags",
|
||||
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,**,or regex",
|
||||
"LABELS": "Labels",
|
||||
"MATCHES_LABELS": "Matches Labels",
|
||||
"MATCHES_EXCEPT_LABELS": "Matches except Labels",
|
||||
"REP_LABELS": "Enter multiple comma separated labels",
|
||||
"RETENTION_RUN": "Retention Run",
|
||||
"RETENTION_RUN_EXPLAIN": "Executing the retention policy can have adverse effects to the images in this project and affected image tags will be deleted. Press CANCEL and use a DRY RUN to simulate the effect of this policy. Otherwise press RUN to proceed.",
|
||||
"RETENTION_RUN_ABORTED": "Retention Run Aborted",
|
||||
"RETENTION_RUN_ABORTED_EXPLAIN": "This retention run has been aborted. Images already deleted are irreversible. You can initiate another run to continue to delete images. In order to simulate a run, you can use the “DRY RUN”.",
|
||||
"LOADING": "Loading...",
|
||||
"NO_EXECUTION": "We couldn't find any executions!",
|
||||
"NO_HISTORY": "We couldn't find any histories!",
|
||||
"DELETION": "Deletions",
|
||||
"EDIT_TITLE": "Edit Tag Retention Rule",
|
||||
"LOG": "Log",
|
||||
"EXCLUDES": "Excludes",
|
||||
"MATCHES": "Matches",
|
||||
"REPO": " repositories",
|
||||
"EXC": " excluding ",
|
||||
"MAT": " matching ",
|
||||
"AND": " and",
|
||||
"WITH": " with ",
|
||||
"WITHOUT": " without ",
|
||||
"LOWER_LABELS": " labels",
|
||||
"WITH_CONDITION": " with",
|
||||
"LOWER_TAGS": " tags"
|
||||
}
|
||||
|
||||
|
||||
|
@ -43,7 +43,9 @@
|
||||
"ACTIONS": "操作",
|
||||
"BROWSE": "选择文件",
|
||||
"UPLOAD": "上传",
|
||||
"NO_FILE": "未选择文件"
|
||||
"NO_FILE": "未选择文件",
|
||||
"ADD": "添加",
|
||||
"RUN": "执行"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "删除成功",
|
||||
@ -1086,6 +1088,76 @@
|
||||
"SYS_WHITELIST": "启用系统白名单",
|
||||
"PRO_WHITELIST": "启用项目白名单",
|
||||
"ADD_SYSTEM": "添加系统白名单"
|
||||
},
|
||||
"TAG_RETENTION": {
|
||||
"TAG_RETENTION": "Tag保留策略",
|
||||
"RETENTION_RULES": "保留规则",
|
||||
"RULE_NAME_1": "最近{{number}}天的镜像",
|
||||
"RULE_NAME_2": "最近活跃的{{number}}个镜像",
|
||||
"RULE_NAME_3": "最近推送的{{number}}个镜像",
|
||||
"RULE_NAME_4": "最近拉取的{{number}}个镜像",
|
||||
"RULE_NAME_5": "全部镜像",
|
||||
"ADD_RULE": "添加规则",
|
||||
"ADD_RULE_HELP_1": "点击添加按钮可添加规则",
|
||||
"ADD_RULE_HELP_2": "Tag保留策略每天运行一次.",
|
||||
"RETENTION_RUNS": "运行保留策略",
|
||||
"RUN_NOW": "立即运行",
|
||||
"WHAT_IF_RUN": "模拟运行",
|
||||
"ABORT": "中止",
|
||||
"SERIAL": "ID",
|
||||
"STATUS": "状态",
|
||||
"DRY_RUN": "模拟运行",
|
||||
"START_TIME": "开始时间",
|
||||
"DURATION": "持续时间",
|
||||
"DETAILS": "详情",
|
||||
"REPOSITORY": "仓库",
|
||||
"EDIT": "编辑",
|
||||
"DISABLE": "禁用",
|
||||
"ENABLE": "启用",
|
||||
"DELETE": "删除",
|
||||
"ADD_TITLE": "添加Tag保留规则",
|
||||
"ADD_SUBTITLE": "为当前项目指定tag保留规则。所有tag保留规则独立计算并且适用于所有符合条件的仓库。",
|
||||
"BY_WHAT": "以镜像或天数为条件",
|
||||
"RULE_TEMPLATE_1": "最近#天的镜像",
|
||||
"RULE_TEMPLATE_2": "最近活跃的#个镜像",
|
||||
"RULE_TEMPLATE_3": "最近推送的#个镜像",
|
||||
"RULE_TEMPLATE_4": "最近拉取的#个镜像",
|
||||
"RULE_TEMPLATE_5": "全部",
|
||||
"ACTION_RETAIN": "保留",
|
||||
"UNIT_DAY": "天数",
|
||||
"UNIT_COUNT": "个数",
|
||||
"NUMBER": "数量",
|
||||
"IN_REPOSITORIES": "仓库",
|
||||
"REP_SEPARATOR": "使用逗号分隔repos,repo*和**",
|
||||
"TAGS": "Tags",
|
||||
"MATCHES_TAGS": "匹配tags",
|
||||
"MATCHES_EXCEPT_TAGS": "反匹配tags",
|
||||
"TAG_SEPARATOR": "使用逗号分割tags,tag*,**,or regex",
|
||||
"LABELS": "标签",
|
||||
"MATCHES_LABELS": "匹配标签",
|
||||
"MATCHES_EXCEPT_LABELS": "反匹配标签",
|
||||
"REP_LABELS": "使用逗号分割标签",
|
||||
"RETENTION_RUN": "运行保留策略",
|
||||
"RETENTION_RUN_EXPLAIN": "执行保留策略将对该项目中的镜像产生反向影响,受影响的镜像tags将会被删除。您可选择取消或者使用模拟运行,或者点击运行以继续。",
|
||||
"RETENTION_RUN_ABORTED": "中止运行保留策略",
|
||||
"RETENTION_RUN_ABORTED_EXPLAIN": "已中止运行保留策略,已删除的镜像不可恢复。您可执行另一个运行命令以便继续删除镜像。如需模拟运行,请点击模拟运行按钮。",
|
||||
"LOADING": "载入中...",
|
||||
"NO_EXECUTION": "暂无记录!",
|
||||
"NO_HISTORY": "暂无记录!",
|
||||
"DELETION": "删除记录",
|
||||
"EDIT_TITLE": "编辑Tag保留规则",
|
||||
"LOG": "日志",
|
||||
"EXCLUDES": "排除",
|
||||
"MATCHES": "匹配",
|
||||
"REPO": "仓库",
|
||||
"EXC": "反匹配",
|
||||
"MAT": "匹配",
|
||||
"AND": "且",
|
||||
"WITH": "有",
|
||||
"WITHOUT": "没有",
|
||||
"LOWER_LABELS": "标签",
|
||||
"WITH_CONDITION": "基于条件",
|
||||
"LOWER_TAGS": "tags"
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user