mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-28 03:27:41 +01:00
Merge pull request #6131 from ninjadq/ui_enhancement_of_chart_label
Enhancement: Harbor Helmchart UI Label
This commit is contained in:
commit
a48ae01e4b
@ -49,7 +49,7 @@
|
||||
</button>
|
||||
<clr-dropdown>
|
||||
<button type="button" class="btn btn-sm btn-secondary" clrDropdownTrigger
|
||||
[disabled]="!(selectedRows.length==1)">
|
||||
[disabled]="!(selectedRows.length===1)">
|
||||
<clr-icon shape="plus" size="16"></clr-icon>{{'REPOSITORY.ADD_LABELS' | translate}}
|
||||
</button>
|
||||
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
|
||||
@ -69,9 +69,17 @@
|
||||
|
||||
<clr-dg-column>{{'HELM_CHART.MAINTAINERS' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{'HELM_CHART.CREATED' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.LABELS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>
|
||||
{{'REPOSITORY.LABELS' | translate}}
|
||||
<clr-dg-filter [clrDgFilter]="labelFilter">
|
||||
<hbr-chart-version-label-filter #labelFilter class="label-filter"
|
||||
[labels]="projectLabels"
|
||||
[resourceType]="resourceType">
|
||||
</hbr-chart-version-label-filter>
|
||||
</clr-dg-filter>
|
||||
</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'HELM_CHART.NO_VERSION_PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let v of chartVersions" [clrDgItem]="v">
|
||||
<clr-dg-row *clrDgItems="let v of chartVersions" [clrDgItem]="v">
|
||||
<clr-dg-cell>
|
||||
<span class="list-img">
|
||||
<img [src]="v.icon ?v.icon:chartDefaultIcon" (error)="getDefaultIcon(v);" />
|
||||
@ -83,8 +91,10 @@
|
||||
<clr-dg-cell>{{ getMaintainerString(v.maintainers) }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{ v.created | date}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<hbr-label-piece *ngIf="v.labels?.length" [label]="v.labels[0]"> </hbr-label-piece>
|
||||
<hbr-resource-label-signpost *ngIf="v.labels?.length>1" [labels]="v.labels"></hbr-resource-label-signpost>
|
||||
<div>
|
||||
<hbr-label-piece *ngIf="v.labels?.length" [label]="v.labels[0]" [labelWidth]="130"> </hbr-label-piece>
|
||||
<hbr-resource-label-signpost *ngIf="v.labels?.length>1" [labels]="v.labels"></hbr-resource-label-signpost>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
|
@ -34,13 +34,15 @@
|
||||
}
|
||||
|
||||
clr-dg-action-bar {
|
||||
clr-dropdown{
|
||||
clr-dropdown {
|
||||
@include dropdown-as-action-button;
|
||||
}
|
||||
}
|
||||
|
||||
hbr-resource-label-signpost {
|
||||
display: inline-block;
|
||||
max-height: 24px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
|
@ -317,8 +317,8 @@ export class ChartVersionComponent implements OnInit {
|
||||
.subscribe(labels => {
|
||||
let versionIdx = this.chartVersions.findIndex(v => v.name === version.name);
|
||||
this.chartVersions[versionIdx].labels = labels;
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 2000);
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 200);
|
||||
setTimeout(() => clearInterval(hnd), 5000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ import { Type } from '@angular/core';
|
||||
import { LabelComponent} from "./label.component";
|
||||
import { LabelMarkerComponent } from './label-marker/label-marker.component';
|
||||
import { LabelSignPostComponent } from './label-signpost/label-signpost.component';
|
||||
import { LabelFilterComponent } from './label-filter/label-filter.component';
|
||||
|
||||
export const LABEL_DIRECTIVES: Type<any>[] = [
|
||||
LabelComponent,
|
||||
LabelMarkerComponent,
|
||||
LabelSignPostComponent
|
||||
LabelSignPostComponent,
|
||||
LabelFilterComponent
|
||||
];
|
||||
|
@ -0,0 +1,18 @@
|
||||
<div>
|
||||
<div class="form-group filter-div">
|
||||
<input #filterInput type="text" placeholder='{{"LABEL.FILTER_LABEL_PLACEHOLDER" | translate }}' [(ngModel)]="labelFilter">
|
||||
</div>
|
||||
<div class="label-items-container">
|
||||
<div class="dropdown-item" *ngFor='let label of filteredLabels'>
|
||||
<span class="check-label-span">
|
||||
<clr-icon *ngIf="isSelected(label)" shape="check"></clr-icon>
|
||||
</span>
|
||||
<span (click)="selectLabel(label)">
|
||||
<hbr-label-piece [label]="label" [labelWidth]="90"></hbr-label-piece>
|
||||
</span>
|
||||
<span class="x-label-span" (click)="unselectLabel(label)">
|
||||
<clr-icon *ngIf="isSelected(label)" shape="times-circle"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,31 @@
|
||||
@mixin icon-span {
|
||||
width: 12px;
|
||||
min-height: 12px;
|
||||
}
|
||||
|
||||
.filter-div {
|
||||
margin-left: 9px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.label-items-container {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
.dropdown-item {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
|
||||
.x-label-span {
|
||||
@include icon-span();
|
||||
float: right;
|
||||
}
|
||||
|
||||
.check-label-span {
|
||||
@include icon-span();
|
||||
float: left;
|
||||
margin-right: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
import { OnInit, Input, EventEmitter, Component, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||
import {ClrDatagridFilterInterface} from "@clr/angular";
|
||||
import { fromEvent } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
|
||||
import { Label, Tag } from './../../service/interface';
|
||||
import { HelmChartVersion } from '../../service/interface';
|
||||
import { ResourceType } from '../../shared/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: "hbr-chart-version-label-filter",
|
||||
templateUrl: './label-filter.component.html',
|
||||
styleUrls: ['./label-filter.component.scss']
|
||||
})
|
||||
export class LabelFilterComponent implements ClrDatagridFilterInterface<any>, OnInit {
|
||||
|
||||
@Input() labels: Label[] = [];
|
||||
@Input() resourceType: ResourceType;
|
||||
|
||||
@ViewChild('filterInput') filterInputRef: ElementRef;
|
||||
|
||||
selectedLabels: Map<number, boolean> = new Map<number, boolean>();
|
||||
|
||||
changes: EventEmitter<any> = new EventEmitter<any>(false);
|
||||
|
||||
labelFilter = '';
|
||||
|
||||
ngOnInit(): void {
|
||||
fromEvent(this.filterInputRef.nativeElement, 'keyup')
|
||||
.pipe(debounceTime(500))
|
||||
.subscribe(() => {
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 2000);
|
||||
});
|
||||
}
|
||||
constructor(private cdr: ChangeDetectorRef) {}
|
||||
|
||||
get filteredLabels() {
|
||||
return this.labels.filter(label => label.name.includes(this.labelFilter));
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.selectedLabels.size > 0;
|
||||
}
|
||||
|
||||
accepts(cv: any): boolean {
|
||||
if (this.resourceType === ResourceType.CHART_VERSION) {
|
||||
return (cv as HelmChartVersion).labels.some(label => this.selectedLabels.get(label.id));
|
||||
} else if (this.resourceType === ResourceType.REPOSITORY_TAG) {
|
||||
return (cv as Tag).labels.some(label => this.selectedLabels.get(label.id));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
selectLabel(label: Label) {
|
||||
this.selectedLabels.set(label.id, true);
|
||||
this.changes.emit();
|
||||
}
|
||||
|
||||
unselectLabel(label: Label) {
|
||||
this.selectedLabels.delete(label.id);
|
||||
this.changes.emit(true);
|
||||
}
|
||||
|
||||
isSelected(label: Label) {
|
||||
return this.selectedLabels.has(label.id);
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@
|
||||
|
||||
.filter-div {
|
||||
margin-left: 9px;
|
||||
margin-right: 9px;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.label-items-container {
|
||||
|
@ -1,8 +1,6 @@
|
||||
<div class="trigger-item">
|
||||
<clr-signpost>
|
||||
<button class="btn btn-link" clrSignpostTrigger>
|
||||
...
|
||||
</button>
|
||||
<button class="btn btn-link" clrSignpostTrigger>...</button>
|
||||
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
|
||||
<div *ngFor="let label of labels">
|
||||
<hbr-label-piece [label]="label" [labelWidth]="130"></hbr-label-piece>
|
||||
|
@ -4,6 +4,7 @@ clr-signpost {
|
||||
margin: 0;
|
||||
height: 24px;
|
||||
vertical-align: super;
|
||||
line-height: 6px;
|
||||
}
|
||||
|
||||
clr-signpost-content {
|
||||
|
@ -104,4 +104,5 @@ export enum Roles {
|
||||
export enum ResourceType {
|
||||
REPOSITORY = 1,
|
||||
CHART_VERSION = 2,
|
||||
REPOSITORY_TAG = 3,
|
||||
}
|
||||
|
@ -25,28 +25,34 @@
|
||||
<div class="row" style="position:relative;">
|
||||
<div>
|
||||
<div class="row flex-items-xs-right rightPos">
|
||||
<div id="filterArea">
|
||||
<div class='filterLabelPiece' *ngIf="!withAdmiral" [hidden]="!openLabelFilterPiece" [style.left.px]='filterLabelPieceWidth' ><hbr-label-piece [hidden]='!filterOneLabel' [label]="filterOneLabel" [labelWidth]="130"></hbr-label-piece></div>
|
||||
<div class="flex-xs-middle">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filterEvt)="doSearchTagNames($event)" (openFlag)="openFlagEvent($event)" [currentValue]="lastFilteredTagName"></hbr-filter>
|
||||
<div class="labelFilterPanel" *ngIf="!withAdmiral" [hidden]="!openLabelFilterPanel">
|
||||
<a class="filterClose" (click)="closeFilter()">×</a>
|
||||
<label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label>
|
||||
<div class="form-group"><input type="text" placeholder="Filter labels" [(ngModel)]= "filterName" (keyup)="handleInputFilter()"></div>
|
||||
<div [hidden]='imageFilterLabels.length' style="padding-left:10px;">{{'LABEL.NO_LABELS' | translate }}</div>
|
||||
<div [hidden]='!imageFilterLabels.length' style='max-height:300px;overflow-y: auto;'>
|
||||
<button type="button" class="labelBtn" *ngFor='let label of imageFilterLabels' [hidden]="!label.show" (click)="rightFilterLabel(label)">
|
||||
<div id="filterArea">
|
||||
<div class='filterLabelPiece' *ngIf="!withAdmiral" [hidden]="!openLabelFilterPiece" [style.left.px]='filterLabelPieceWidth'>
|
||||
<hbr-label-piece [hidden]='!filterOneLabel' [label]="filterOneLabel" [labelWidth]="130"></hbr-label-piece>
|
||||
</div>
|
||||
<div class="flex-xs-middle">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'TAG.FILTER_FOR_TAGS' | translate}}" (filterEvt)="doSearchTagNames($event)"
|
||||
(openFlag)="openFlagEvent($event)" [currentValue]="lastFilteredTagName"></hbr-filter>
|
||||
<div class="labelFilterPanel" *ngIf="!withAdmiral" [hidden]="!openLabelFilterPanel">
|
||||
<a class="filterClose" (click)="closeFilter()">×</a>
|
||||
<label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label>
|
||||
<div class="form-group"><input type="text" placeholder="Filter labels" [(ngModel)]="filterName" (keyup)="handleInputFilter()"></div>
|
||||
<div [hidden]='imageFilterLabels.length' style="padding-left:10px;">{{'LABEL.NO_LABELS' | translate }}</div>
|
||||
<div [hidden]='!imageFilterLabels.length' style='max-height:300px;overflow-y: auto;'>
|
||||
<button type="button" class="labelBtn" *ngFor='let label of imageFilterLabels' [hidden]="!label.show"
|
||||
(click)="rightFilterLabel(label)">
|
||||
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'></clr-icon>
|
||||
<div class='labelDiv'><hbr-label-piece [label]="label.label" [labelWidth]="160"></hbr-label-piece></div>
|
||||
</button>
|
||||
<div class='labelDiv'>
|
||||
<hbr-label-piece [label]="label.label" [labelWidth]="160"></hbr-label-piece>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="refresh-btn" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid [clrDgLoading]="loading" [class.embeded-datagrid]="isEmbedded" [(clrDgSelected)]="selectedRow">
|
||||
|
@ -64,4 +64,9 @@
|
||||
height: 40px;
|
||||
width: 1px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
#results {
|
||||
overflow-y: auto;
|
||||
max-height: 99%;
|
||||
}
|
@ -21,8 +21,7 @@ export class ListChartVersionRoComponent implements OnInit {
|
||||
private projectService: ProjectService,
|
||||
private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
ngOnInit() {}
|
||||
|
||||
getStatusString(chart: HelmChartVersion) {
|
||||
if (chart.deprecated) {
|
||||
|
@ -770,7 +770,7 @@
|
||||
"DELETE": "Delete",
|
||||
"LABEL_NAME": "Label Name",
|
||||
"COLOR": "Color",
|
||||
"FILTER_Label_PLACEHOLDER": "Filter Labels",
|
||||
"FILTER_LABEL_PLACEHOLDER": "Filter Labels",
|
||||
"NO_LABELS": "No labels",
|
||||
"DELETION_TITLE_TARGET": "Confirm Label Deletion",
|
||||
"DELETION_SUMMARY_TARGET": "Do you want to delete {{param}}?",
|
||||
|
@ -733,7 +733,7 @@
|
||||
"DELETE": "Delete",
|
||||
"LABEL_NAME": "Label Name",
|
||||
"COLOR": "Color",
|
||||
"FILTER_Label_PLACEHOLDER": "Filter Labels",
|
||||
"FILTER_LABEL_PLACEHOLDER": "Filter Labels",
|
||||
"NO_LABELS": "No labels",
|
||||
"DELETION_TITLE_TARGET": "Confirm Label Deletion",
|
||||
"DELETION_SUMMARY_TARGET": "Do you want to delete {{param}}?",
|
||||
|
@ -768,7 +768,7 @@
|
||||
"DELETE": "删除",
|
||||
"LABEL_NAME": "标签名字",
|
||||
"COLOR": "颜色",
|
||||
"FILTER_Label_PLACEHOLDER": "过滤标签",
|
||||
"FILTER_LABEL_PLACEHOLDER": "过滤标签",
|
||||
"NO_LABELS": "无标签",
|
||||
"DELETION_TITLE_TARGET": "删除标签确认",
|
||||
"DELETION_SUMMARY_TARGET": "确认删除标签 {{param}}?",
|
||||
|
Loading…
Reference in New Issue
Block a user