Modify tag-retention UI

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
AllForNothing 2019-08-01 18:12:44 +08:00
parent 27245021d3
commit beed897170
11 changed files with 145 additions and 87 deletions

View File

@ -12,9 +12,10 @@ import {
clone
} from '../utils';
import { ErrorHandler } from '../error-handler/index';
import { SystemSettingsComponent, VulnerabilityConfigComponent, GcComponent} from './index';
import { Configuration } from './config';
import { map, catchError } from "rxjs/operators";
import { VulnerabilityConfigComponent } from "./vulnerability/vulnerability-config.component";
import { GcComponent } from "./gc";
import { SystemSettingsComponent } from "./system/system-settings.component";
@Component({
selector: 'hbr-registry-config',

View File

@ -4,6 +4,32 @@
<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 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 [(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">
@ -21,40 +47,14 @@
</div>
<div class="clr-col-3">
<div class="over-line">
<span *ngIf="template !=='always'">{{getI18nKey(unit)|translate}}</span>
<span *ngIf="template !=='always' && template !=='none'">{{getI18nKey(unit)|translate}}</span>
</div>
<div class="w-100 disabled">
<input *ngIf="template !=='always'" [(ngModel)]="num" class="clr-input w-100">
<input *ngIf="template !=='always'&& template !=='none'" [(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">

View File

@ -39,7 +39,7 @@ export class Retention {
}
export class Rule {
isDisabled: boolean;
disabled: boolean;
id: number;
priority: number;
action: string;
@ -51,6 +51,7 @@ export class Rule {
};
constructor() {
this.disabled = false;
this.action = "retain";
this.params = {};
this.scope_selectors = {

View File

@ -1,7 +1,7 @@
<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">{{retention?.rules?.length ? retention?.rules?.length : 0}}</span>
class="badge badge-3 ml-5">{{retention?.rules?.length ? retention?.rules?.length : 0}}/15</span>
<span *ngIf="loadingRule" class="spinner spinner-inline ml-2">Loading...</span>
</div>
</div>
@ -10,15 +10,35 @@
<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">
<div class="clr-col-1">
<clr-icon *ngIf="!rule?.disabled" class="color-green" shape="success-standard"></clr-icon>
<clr-icon *ngIf="rule?.disabled" class="color-red" shape="error-standard"></clr-icon>
</div>
<div class="clr-col">
<span>
<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 *ngIf="!rule?.disabled" type="button" class="dropdown-item"
(click)="toggleDisable(i,true)">{{'TAG_RETENTION.DISABLE' | translate}}</button>
<button *ngIf="rule?.disabled" type="button" class="dropdown-item"
(click)="toggleDisable(i,false)">{{'TAG_RETENTION.ENABLE' | translate}}</button>
<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>
</span>
<span class="rule-name">
<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>{{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>
@ -30,18 +50,6 @@
</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>
@ -50,7 +58,7 @@
{{'TAG_RETENTION.ADD_RULE_HELP_1' | translate}}
</div>
<div class="clr-col">
<button class="btn btn-link" (click)="openAddRule()">{{'TAG_RETENTION.ADD_RULE' | translate}}</button>
<button [disabled]="retention?.rules?.length >= 15" class="btn btn-link" (click)="openAddRule()">{{'TAG_RETENTION.ADD_RULE' | translate}}</button>
</div>
</div>
</div>
@ -77,18 +85,15 @@
<clr-icon shape="refresh"></clr-icon>
</button>
</clr-dg-action-bar>
<clr-datagrid [clrDgLoading]="loadingExecutions" [(clrDgSingleSelected)]="selectedItem">
<clr-datagrid (clrDgRefresh)="clrLoad()" [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}}
@ -99,7 +104,7 @@
<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-row *ngFor="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>
@ -114,6 +119,7 @@
<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.RETAINED' | translate}}/{{'TAG_RETENTION.TOTAL' | 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>
@ -123,6 +129,7 @@
<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.retained}}/{{task.total}}</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)"
@ -141,12 +148,12 @@
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}}
<span *ngIf="totalCount">{{pagination?.firstItem + 1}}
-
{{pagination.lastItem + 1 }} {{'ROBOT_ACCOUNT.OF' |
{{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-pagination [clrDgTotalItems]="totalCount" #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>

View File

@ -58,7 +58,7 @@ export class TagRetentionComponent implements OnInit {
projectId: number;
isRetentionRunOpened: boolean = false;
isAbortedOpened: boolean = false;
selectedItem: any;
selectedItem: any = null;
ruleIndex: number = -1;
index: number = -1;
retentionId: number;
@ -66,10 +66,13 @@ export class TagRetentionComponent implements OnInit {
editIndex: number;
executionList = [];
historyList = [];
loadingExecutions: boolean = false;
loadingHistories: boolean = false;
loadingExecutions: boolean = true;
loadingHistories: boolean = true;
label: string = 'TAG_RETENTION.TRIGGER';
loadingRule: boolean = false;
currentPage: number = 1;
pageSize: number = 10;
totalCount: number = 0;
@ViewChild('cronScheduleComponent')
cronScheduleComponent: CronScheduleComponent;
@ViewChild('addRule') addRuleComponent: AddRuleComponent;
@ -114,7 +117,6 @@ export class TagRetentionComponent implements OnInit {
}
this.getRetention();
this.getMetadata();
this.refreshList();
}
updateCron(cron: string) {
let retention: Retention = clone(this.retention);
@ -167,7 +169,20 @@ export class TagRetentionComponent implements OnInit {
this.addRuleComponent.isAdd = false;
this.ruleIndex = -1;
}
toggleDisable(index, isActionDisable) {
let retention: Retention = clone(this.retention);
retention.rules[index].disabled = isActionDisable;
this.ruleIndex = -1;
this.loadingRule = true;
this.tagRetentionService.updateRetention(this.retentionId, retention).subscribe(
response => {
this.loadingRule = false;
this.retention = retention;
}, error => {
this.loadingRule = false;
this.errorHandler.error(error);
});
}
deleteRule(index) {
let retention: Retention = clone(this.retention);
retention.rules.splice(index, 1);
@ -210,17 +225,27 @@ export class TagRetentionComponent implements OnInit {
refreshList() {
this.index = -1 ;
this.selectedItem = null;
this.loadingExecutions = true;
if (this.retentionId) {
this.loadingExecutions = true;
this.tagRetentionService.getRunNowList(this.retentionId).subscribe(
res => {
this.tagRetentionService.getRunNowList(this.retentionId, this.currentPage, this.pageSize).subscribe(
response => {
// Get total count
if (response.headers) {
let xHeader: string = response.headers.get("x-total-count");
if (xHeader) {
this.totalCount = parseInt(xHeader, 0);
}
}
this.executionList = response.body as Array<any>;
this.loadingExecutions = false;
this.executionList = res;
TagRetentionComponent.calculateDuration(this.executionList);
}, error => {
this.loadingExecutions = false;
this.errorHandler.error(error);
});
} else {
this.loadingExecutions = false;
}
}
@ -347,4 +372,7 @@ export class TagRetentionComponent implements OnInit {
getI18nKey(str: string) {
return this.tagRetentionService.getI18nKey(str);
}
clrLoad() {
this.refreshList();
}
}

View File

@ -12,11 +12,12 @@
// 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 { HttpClient, HttpParams, HttpResponse } 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";
import { buildHttpRequestOptionsWithObserveResponse } from "@harbor/ui";
@Injectable()
export class TagRetentionService {
@ -39,6 +40,7 @@ export class TagRetentionService {
"withoutLabels": "WITHOUT",
"COUNT": "UNIT_COUNT",
"DAYS": "UNIT_DAY",
"none": "NONE"
};
constructor(
@ -96,10 +98,14 @@ export class TagRetentionService {
.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)));
getRunNowList(retentionId, page: number, pageSize: number) {
let params = new HttpParams();
if (page && pageSize) {
params = params.set('page', page + '').set('page_size', pageSize + '');
}
return this.http
.get<HttpResponse<Array<any>>>(`/api/retentions/${retentionId}/executions`, buildHttpRequestOptionsWithObserveResponse(params))
.pipe(catchError(error => observableThrowError(error)), );
}
getExecutionHistory(retentionId, executionId) {

View File

@ -1128,11 +1128,11 @@
"RULE_TEMPLATE_3": "the most recently pushed # images",
"RULE_TEMPLATE_4": "the most recently pulled # images",
"RULE_TEMPLATE_5": "always",
"ACTION_RETAIN": "Retain",
"ACTION_RETAIN": " retain",
"UNIT_DAY": "DAYS",
"UNIT_COUNT": "COUNT",
"NUMBER": "NUMBER",
"IN_REPOSITORIES": "Repositories",
"IN_REPOSITORIES": "For the repositories",
"REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **",
"TAGS": "Tags",
"MATCHES_TAGS": "Matches tags",
@ -1163,7 +1163,10 @@
"LOWER_LABELS": " labels",
"WITH_CONDITION": " with",
"LOWER_TAGS": " tags",
"TRIGGER": "Schedule"
"TRIGGER": "Schedule",
"RETAINED": "Retained",
"TOTAL": "Total",
"NONE": "none"
}
}

View File

@ -1126,11 +1126,11 @@
"RULE_TEMPLATE_3": "the most recently pushed # images",
"RULE_TEMPLATE_4": "the most recently pulled # images",
"RULE_TEMPLATE_5": "always",
"ACTION_RETAIN": "Retain",
"ACTION_RETAIN": " retain",
"UNIT_DAY": "DAYS",
"UNIT_COUNT": "COUNT",
"NUMBER": "NUMBER",
"IN_REPOSITORIES": "Repositories",
"IN_REPOSITORIES": "For the repositories",
"REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **",
"TAGS": "Tags",
"MATCHES_TAGS": "Matches tags",
@ -1161,7 +1161,10 @@
"LOWER_LABELS": " labels",
"WITH_CONDITION": " with",
"LOWER_TAGS": " tags",
"TRIGGER": "Schedule"
"TRIGGER": "Schedule",
"RETAINED": "Retained",
"TOTAL": "Total",
"NONE": "none"
}
}

View File

@ -1098,11 +1098,11 @@
"RULE_TEMPLATE_3": "the most recently pushed # images",
"RULE_TEMPLATE_4": "the most recently pulled # images",
"RULE_TEMPLATE_5": "always",
"ACTION_RETAIN": "Retain",
"ACTION_RETAIN": " retain",
"UNIT_DAY": "DAYS",
"UNIT_COUNT": "COUNT",
"NUMBER": "NUMBER",
"IN_REPOSITORIES": "Repositories",
"IN_REPOSITORIES": "For the repositories",
"REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **",
"TAGS": "Tags",
"MATCHES_TAGS": "Matches tags",
@ -1133,7 +1133,10 @@
"LOWER_LABELS": " labels",
"WITH_CONDITION": " with",
"LOWER_TAGS": " tags",
"TRIGGER": "Schedule"
"TRIGGER": "Schedule",
"RETAINED": "Retained",
"TOTAL": "Total",
"NONE": "none"
}
}

View File

@ -1123,11 +1123,11 @@
"RULE_TEMPLATE_3": "the most recently pushed # images",
"RULE_TEMPLATE_4": "the most recently pulled # images",
"RULE_TEMPLATE_5": "always",
"ACTION_RETAIN": "Retain",
"ACTION_RETAIN": " retain",
"UNIT_DAY": "DAYS",
"UNIT_COUNT": "COUNT",
"NUMBER": "NUMBER",
"IN_REPOSITORIES": "Repositories",
"IN_REPOSITORIES": "For the repositories",
"REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **",
"TAGS": "Tags",
"MATCHES_TAGS": "Matches tags",
@ -1158,7 +1158,10 @@
"LOWER_LABELS": " labels",
"WITH_CONDITION": " with",
"LOWER_TAGS": " tags",
"TRIGGER": "Schedule"
"TRIGGER": "Schedule",
"RETAINED": "Retained",
"TOTAL": "Total",
"NONE": "none"
}

View File

@ -1124,11 +1124,11 @@
"RULE_TEMPLATE_3": "最近推送的#个镜像",
"RULE_TEMPLATE_4": "最近拉取的#个镜像",
"RULE_TEMPLATE_5": "全部",
"ACTION_RETAIN": "保留",
"ACTION_RETAIN": " 保留",
"UNIT_DAY": "天数",
"UNIT_COUNT": "个数",
"NUMBER": "数量",
"IN_REPOSITORIES": "仓库",
"IN_REPOSITORIES": "适用于仓库",
"REP_SEPARATOR": "使用逗号分隔repos,repo*和**",
"TAGS": "Tags",
"MATCHES_TAGS": "匹配tags",
@ -1159,7 +1159,10 @@
"LOWER_LABELS": "标签",
"WITH_CONDITION": "基于条件",
"LOWER_TAGS": "tags",
"TRIGGER": "定时执行"
"TRIGGER": "定时执行",
"RETAINED": "保留数",
"TOTAL": "总数",
"NONE": "空"
}
}