mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 17:47:46 +01:00
fix issue about replication rule delete and username autocomplete and… …
This commit is contained in:
parent
be0b31e5ba
commit
148c6ecc53
@ -26,7 +26,7 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ReplicationService } from '../service/replication.service';
|
||||
import { ReplicationRule } from '../service/interface';
|
||||
import {ReplicationJob, ReplicationJobItem, ReplicationRule} from '../service/interface';
|
||||
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
||||
@ -70,6 +70,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
|
||||
rules: ReplicationRule[];
|
||||
changedRules: ReplicationRule[];
|
||||
ruleName: string;
|
||||
canDeleteRule: boolean;
|
||||
|
||||
@ViewChild('toggleConfirmDialog')
|
||||
toggleConfirmDialog: ConfirmationDialogComponent;
|
||||
@ -199,15 +200,48 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
|
||||
this.toggleConfirmDialog.open(toggleConfirmMessage);
|
||||
}
|
||||
|
||||
jobList(): Promise<void> {
|
||||
let ruleData: ReplicationJobItem[];
|
||||
this.canDeleteRule = true;
|
||||
let count: number = 0;
|
||||
return toPromise<ReplicationJob>(this.replicationService
|
||||
.getJobs(this.selectedId))
|
||||
.then(response => {
|
||||
ruleData = response.data;
|
||||
if (ruleData.length) {
|
||||
ruleData.forEach(job => {
|
||||
if ((job.status === 'pending') || (job.status === 'running') || (job.status === 'retrying')) {
|
||||
count ++;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.canDeleteRule = count > 0 ? false : true;
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
deleteRule(rule: ReplicationRule) {
|
||||
let deletionMessage: ConfirmationMessage = new ConfirmationMessage(
|
||||
'REPLICATION.DELETION_TITLE',
|
||||
'REPLICATION.DELETION_SUMMARY',
|
||||
rule.name || '',
|
||||
rule.id,
|
||||
ConfirmationTargets.POLICY,
|
||||
ConfirmationButtons.DELETE_CANCEL);
|
||||
this.deletionConfirmDialog.open(deletionMessage);
|
||||
this.jobList().then(() => {
|
||||
let deletionMessage: ConfirmationMessage;
|
||||
if (!this.canDeleteRule) {
|
||||
deletionMessage = new ConfirmationMessage(
|
||||
'REPLICATION.DELETION_TITLE_FAILURE',
|
||||
'REPLICATION.DELETION_SUMMARY_FAILURE',
|
||||
rule.name || '',
|
||||
rule.id,
|
||||
ConfirmationTargets.POLICY,
|
||||
ConfirmationButtons.CLOSE);
|
||||
} else {
|
||||
deletionMessage = new ConfirmationMessage(
|
||||
'REPLICATION.DELETION_TITLE',
|
||||
'REPLICATION.DELETION_SUMMARY',
|
||||
rule.name || '',
|
||||
rule.id,
|
||||
ConfirmationTargets.POLICY,
|
||||
ConfirmationButtons.DELETE_CANCEL);
|
||||
}
|
||||
this.deletionConfirmDialog.open(deletionMessage);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,7 @@
|
||||
// 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, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild, Input, Output, OnDestroy, EventEmitter } from '@angular/core';
|
||||
import { ResponseOptions, RequestOptions } from '@angular/http';
|
||||
import { NgModel } from '@angular/forms';
|
||||
|
||||
@ -41,6 +41,8 @@ import { REPLICATION_STYLE } from './replication.component.css';
|
||||
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
import { State } from "clarity-angular";
|
||||
import {Observable} from "rxjs/Observable";
|
||||
import {Subscription} from "rxjs/Subscription";
|
||||
|
||||
const ruleStatus: { [key: string]: any } = [
|
||||
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' },
|
||||
@ -79,7 +81,7 @@ export class SearchOption {
|
||||
template: REPLICATION_TEMPLATE,
|
||||
styles: [REPLICATION_STYLE]
|
||||
})
|
||||
export class ReplicationComponent implements OnInit {
|
||||
export class ReplicationComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input() projectId: number | string;
|
||||
@Input() withReplicationJob: boolean;
|
||||
@ -124,6 +126,7 @@ export class ReplicationComponent implements OnInit {
|
||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||
currentState: State;
|
||||
jobsLoading: boolean = false;
|
||||
timerDelay: Subscription;
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
@ -145,6 +148,12 @@ export class ReplicationComponent implements OnInit {
|
||||
this.currentJobSearchOption = 0;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.timerDelay) {
|
||||
this.timerDelay.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
openModal(): void {
|
||||
this.createEditPolicyComponent.openCreateEditRule(true);
|
||||
}
|
||||
@ -197,6 +206,23 @@ export class ReplicationComponent implements OnInit {
|
||||
this.totalCount = response.metadata.xTotalCount;
|
||||
this.jobs = response.data;
|
||||
|
||||
if (!this.timerDelay) {
|
||||
this.timerDelay = Observable.timer(10000, 10000).subscribe(() => {
|
||||
let count: number = 0;
|
||||
this.jobs.forEach((job) => {
|
||||
if ((job.status === 'pending') || (job.status === 'running') || (job.status === 'retrying')) {
|
||||
count ++;
|
||||
}
|
||||
});
|
||||
if (count > 0) {
|
||||
this.clrLoadJobs(this.currentState);
|
||||
}else {
|
||||
this.timerDelay.unsubscribe();
|
||||
this.timerDelay = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Do filtering and sorting
|
||||
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
|
||||
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);
|
||||
|
@ -75,6 +75,7 @@ describe('RepositoryComponentStackview (inline template)', () => {
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
|
@ -51,6 +51,7 @@ export interface Repository {
|
||||
export interface Tag extends Base {
|
||||
digest: string;
|
||||
name: string;
|
||||
size: string;
|
||||
architecture: string;
|
||||
os: string;
|
||||
docker_version: string;
|
||||
|
@ -14,6 +14,7 @@ describe('TagService', () => {
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
|
@ -43,6 +43,7 @@ describe('TagDetailComponent (inline template)', () => {
|
||||
let mockTag: Tag = {
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "nginx",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
|
@ -24,6 +24,7 @@ export class TagDetailComponent implements OnInit {
|
||||
@Input() repositoryId: string;
|
||||
tagDetails: Tag = {
|
||||
name: "--",
|
||||
size: "--",
|
||||
author: "--",
|
||||
created: new Date(),
|
||||
architecture: "--",
|
||||
|
@ -43,4 +43,9 @@ export const TAG_STYLE = `
|
||||
color: red;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
:host >>> .datagrid clr-dg-column {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
`;
|
@ -16,14 +16,13 @@ export const TAG_TEMPLATE = `
|
||||
<h2 *ngIf="!isEmbedded" class="sub-header-title">{{repoName}}</h2>
|
||||
<clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded">
|
||||
<clr-dg-column style="width: 80px;" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 80px;" [clrDgField]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="min-width: 180px;">{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 160px;" *ngIf="withClair">{{'VULNERABILITY.SINGULAR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 80px;" *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 100px;">{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 160px;"[clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 80px;" [clrDgField]="'docker_version'" *ngIf="!withClair">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 80px;" [clrDgField]="'architecture'" *ngIf="!withClair">{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column style="width: 80px;" [clrDgField]="'os'" *ngIf="!withClair">{{'REPOSITORY.OS' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'TGA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-action-overflow>
|
||||
@ -35,6 +34,7 @@ export const TAG_TEMPLATE = `
|
||||
<a *ngSwitchCase="true" href="javascript:void(0)" (click)="onTagClick(t)">{{t.name}}</a>
|
||||
<span *ngSwitchDefault>{{t.name}}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 80px;">{{t.size}}</clr-dg-cell>
|
||||
<clr-dg-cell style="min-width: 180px;" class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}">docker pull {{registryUrl}}/{{repoName}}:{{t.name}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 160px;" *ngIf="withClair">
|
||||
<hbr-vulnerability-bar [repoName]="repoName" [tagId]="t.name" [summary]="t.scan_overview"></hbr-vulnerability-bar>
|
||||
@ -50,8 +50,6 @@ export const TAG_TEMPLATE = `
|
||||
<clr-dg-cell style="width: 100px;">{{t.author}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 160px;">{{t.created | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.docker_version}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.architecture}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.os}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
|
||||
|
@ -29,6 +29,7 @@ describe('TagComponent (inline template)', () => {
|
||||
{
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "1.11.5",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
|
@ -159,6 +159,9 @@ export class TagComponent implements OnInit {
|
||||
if (t.signature !== null) {
|
||||
signatures.push(t.name);
|
||||
}
|
||||
|
||||
//size
|
||||
t.size = this.sizeTransform(t.size);
|
||||
});
|
||||
this.tags = items;
|
||||
let signedName: {[key: string]: string[]} = {};
|
||||
@ -177,6 +180,19 @@ export class TagComponent implements OnInit {
|
||||
setTimeout(() => clearInterval(hnd), 5000);
|
||||
}
|
||||
|
||||
sizeTransform(tagSize: string): string {
|
||||
let size: number = Number.parseInt(tagSize);
|
||||
if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) {
|
||||
return (size / Math.pow(1024, 1)).toFixed(2) + 'KB';
|
||||
} else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) {
|
||||
return (size / Math.pow(1024, 2)).toFixed(2) + 'MB';
|
||||
} else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) {
|
||||
return (size / Math.pow(1024, 3)).toFixed(2) + 'MB';
|
||||
} else {
|
||||
return size + 'B';
|
||||
}
|
||||
}
|
||||
|
||||
deleteTag(tag: Tag) {
|
||||
if (tag) {
|
||||
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;
|
||||
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.9.8",
|
||||
"clarity-ui": "^0.9.8",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "0.4.60",
|
||||
"harbor-ui": "0.4.71",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -1,4 +1,26 @@
|
||||
.form-group-label-override {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.selectBox{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border: 1px solid #ccc;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0,0,0,.15);
|
||||
border-right-width: 2px;
|
||||
border-bottom-width: 2px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||
z-index: 100;
|
||||
}
|
||||
.selectBox ul li{
|
||||
list-style: none;
|
||||
padding: 3px 20px
|
||||
}
|
||||
.selectBox ul li:hover{
|
||||
color: #262626;
|
||||
background-image: linear-gradient(180deg,#f5f5f5 0,#e8e8e8);
|
||||
background-repeat: repeat-x;
|
||||
}
|
@ -6,16 +6,22 @@
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="member_name" class="col-md-4 form-group-label-override required">{{'MEMBER.NAME' | translate}}</label>
|
||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="!isMemberNameValid" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left">
|
||||
<input type="text" id="member_name" [(ngModel)]="member.username"
|
||||
name="member_name"
|
||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="!isMemberNameValid"
|
||||
class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" (mouseleave)="leaveInput()">
|
||||
<input type="text" id="member_name" [(ngModel)]="member.username"
|
||||
name="member_name"
|
||||
size="20"
|
||||
#memberName="ngModel"
|
||||
required
|
||||
(keyup)='handleValidation()'>
|
||||
(keyup)='handleValidation()' autocomplete="off">
|
||||
<span class="tooltip-content">
|
||||
{{ memberTooltip | translate }}
|
||||
</span>
|
||||
<div class="selectBox" [style.display]="selectUserName.length ? 'block' : 'none'" >
|
||||
<ul>
|
||||
<li *ngFor="let name of selectUserName" (click)="selectedName(name)">{{name}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</label>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
|
||||
</div>
|
||||
|
@ -25,6 +25,7 @@ import { Response } from '@angular/http';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { MemberService } from '../member.service';
|
||||
import { UserService } from '../../../user/user.service';
|
||||
|
||||
import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service';
|
||||
import { InlineAlertComponent } from '../../../shared/inline-alert/inline-alert.component';
|
||||
@ -36,11 +37,13 @@ import { Member } from '../member';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import {User} from "../../../user/user";
|
||||
|
||||
@Component({
|
||||
selector: 'add-member',
|
||||
templateUrl: 'add-member.component.html',
|
||||
styleUrls: ['add-member.component.css'],
|
||||
providers: [UserService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
@ -69,13 +72,21 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
memberTooltip: string = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
nameChecker: Subject<string> = new Subject<string>();
|
||||
checkOnGoing: boolean = false;
|
||||
selectUserName: string[] = [];
|
||||
userLists: User[];
|
||||
|
||||
constructor(private memberService: MemberService,
|
||||
private userService: UserService,
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private translateService: TranslateService,
|
||||
private ref: ChangeDetectorRef) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userService.getUsers()
|
||||
.then(users => {
|
||||
this.userLists = users;
|
||||
});
|
||||
|
||||
this.nameChecker
|
||||
.debounceTime(500)
|
||||
.distinctUntilChanged()
|
||||
@ -97,6 +108,20 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
});
|
||||
//username autocomplete
|
||||
if (this.userLists.length) {
|
||||
this.selectUserName = [];
|
||||
this.userLists.filter(data => {
|
||||
if (data.username.startsWith(cont.value)) {
|
||||
if (this.selectUserName.length < 10) {
|
||||
this.selectUserName.push(data.username);
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
setInterval(() => this.ref.markForCheck(), 100);
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
}
|
||||
@ -148,6 +173,11 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
selectedName(username: string) {
|
||||
this.member.username = username;
|
||||
this.selectUserName = [];
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
if (this.hasChanged) {
|
||||
this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' });
|
||||
@ -157,6 +187,9 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
leaveInput() {
|
||||
this.selectUserName = [];
|
||||
}
|
||||
ngAfterViewChecked(): void {
|
||||
if (this.memberForm !== this.currentForm) {
|
||||
this.memberForm = this.currentForm;
|
||||
@ -189,6 +222,7 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
this.member.username = '';
|
||||
this.isMemberNameValid = true;
|
||||
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
this.selectUserName = [];
|
||||
}
|
||||
|
||||
handleValidation(): void {
|
||||
|
@ -215,6 +215,8 @@
|
||||
"FILTER_JOBS_PLACEHOLDER": "Filter Jobs",
|
||||
"DELETION_TITLE": "Confirm Rule Deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete rule {{param}}?",
|
||||
"DELETION_TITLE_FAILURE": "failed to delete Rule Deletion",
|
||||
"DELETION_SUMMARY_FAILURE": "{{param}} have pending/running/retrying status",
|
||||
"FILTER_TARGETS_PLACEHOLDER": "Filter Endpoints",
|
||||
"DELETION_TITLE_TARGET": "Confirm Endpoint Deletion",
|
||||
"DELETION_SUMMARY_TARGET": "Do you want to delete the endpoint {{param}}?",
|
||||
@ -330,6 +332,7 @@
|
||||
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.\nDelete from Notary via this command:\n{{param}}",
|
||||
"FILTER_FOR_REPOSITORIES": "Filter Repositories",
|
||||
"TAG": "Tag",
|
||||
"SIZE": "Size",
|
||||
"SIGNED": "Signed",
|
||||
"AUTHOR": "Author",
|
||||
"CREATED": "Creation Time",
|
||||
|
@ -215,6 +215,8 @@
|
||||
"FILTER_JOBS_PLACEHOLDER": "Filtrar Trabajos",
|
||||
"DELETION_TITLE": "Confirmar Eliminación de Regla",
|
||||
"DELETION_SUMMARY": "¿Quiere eliminar la regla {{param}}?",
|
||||
"DELETION_TITLE_FAILURE": "failed to delete Rule Deletion",
|
||||
"DELETION_SUMMARY_FAILURE": "{{param}} have pending/running/retrying status",
|
||||
"FILTER_TARGETS_PLACEHOLDER": "Filtrar Endpoints",
|
||||
"DELETION_TITLE_TARGET": "Confirmar Eliminación de Endpoint",
|
||||
"DELETION_SUMMARY_TARGET": "¿Quiere eliminar el endpoint {{param}}?",
|
||||
@ -331,6 +333,7 @@
|
||||
"DELETION_SUMMARY_TAG_DENIED": "La etiqueta debe ser eliminada de la Notaría antes de eliminarla.\nEliminarla de la Notaría con este comando:\n{{param}}",
|
||||
"FILTER_FOR_REPOSITORIES": "Filtrar Repositorios",
|
||||
"TAG": "Etiqueta",
|
||||
"SIZE": "Size",
|
||||
"SIGNED": "Firmada",
|
||||
"AUTHOR": "Autor",
|
||||
"CREATED": "Fecha de creación",
|
||||
|
@ -215,6 +215,8 @@
|
||||
"FILTER_JOBS_PLACEHOLDER": "过滤任务",
|
||||
"DELETION_TITLE": "删除规则确认",
|
||||
"DELETION_SUMMARY": "确认删除规则 {{param}}?",
|
||||
"DELETION_TITLE_FAILURE": "规则确认删除失败",
|
||||
"DELETION_SUMMARY_FAILURE": "{{param}} 有 pending/running/retrying 状态,不能删除",
|
||||
"FILTER_TARGETS_PLACEHOLDER": "过滤目标",
|
||||
"DELETION_TITLE_TARGET": "删除目标确认",
|
||||
"DELETION_SUMMARY_TARGET": "确认删除目标 {{param}}?",
|
||||
@ -330,6 +332,7 @@
|
||||
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n{{param}}",
|
||||
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
|
||||
"TAG": "标签",
|
||||
"SIZE": "大小",
|
||||
"SIGNED": "已签名",
|
||||
"AUTHOR": "作者",
|
||||
"CREATED": "创建时间",
|
||||
|
Loading…
Reference in New Issue
Block a user