mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-29 12:07:56 +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>
|
</button>
|
||||||
<clr-dropdown>
|
<clr-dropdown>
|
||||||
<button type="button" class="btn btn-sm btn-secondary" clrDropdownTrigger
|
<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}}
|
<clr-icon shape="plus" size="16"></clr-icon>{{'REPOSITORY.ADD_LABELS' | translate}}
|
||||||
</button>
|
</button>
|
||||||
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
|
<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.MAINTAINERS' | translate }}</clr-dg-column>
|
||||||
<clr-dg-column>{{'HELM_CHART.CREATED' | 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-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>
|
<clr-dg-cell>
|
||||||
<span class="list-img">
|
<span class="list-img">
|
||||||
<img [src]="v.icon ?v.icon:chartDefaultIcon" (error)="getDefaultIcon(v);" />
|
<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>{{ getMaintainerString(v.maintainers) }}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{ v.created | date}}</clr-dg-cell>
|
<clr-dg-cell>{{ v.created | date}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<hbr-label-piece *ngIf="v.labels?.length" [label]="v.labels[0]"> </hbr-label-piece>
|
<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>
|
<hbr-resource-label-signpost *ngIf="v.labels?.length>1" [labels]="v.labels"></hbr-resource-label-signpost>
|
||||||
|
</div>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
|
@ -34,13 +34,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
clr-dg-action-bar {
|
clr-dg-action-bar {
|
||||||
clr-dropdown{
|
clr-dropdown {
|
||||||
@include dropdown-as-action-button;
|
@include dropdown-as-action-button;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hbr-resource-label-signpost {
|
hbr-resource-label-signpost {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
max-height: 24px;
|
||||||
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-container {
|
.card-container {
|
||||||
|
@ -317,8 +317,8 @@ export class ChartVersionComponent implements OnInit {
|
|||||||
.subscribe(labels => {
|
.subscribe(labels => {
|
||||||
let versionIdx = this.chartVersions.findIndex(v => v.name === version.name);
|
let versionIdx = this.chartVersions.findIndex(v => v.name === version.name);
|
||||||
this.chartVersions[versionIdx].labels = labels;
|
this.chartVersions[versionIdx].labels = labels;
|
||||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
let hnd = setInterval(() => this.cdr.markForCheck(), 200);
|
||||||
setTimeout(() => clearInterval(hnd), 2000);
|
setTimeout(() => clearInterval(hnd), 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ import { Type } from '@angular/core';
|
|||||||
import { LabelComponent} from "./label.component";
|
import { LabelComponent} from "./label.component";
|
||||||
import { LabelMarkerComponent } from './label-marker/label-marker.component';
|
import { LabelMarkerComponent } from './label-marker/label-marker.component';
|
||||||
import { LabelSignPostComponent } from './label-signpost/label-signpost.component';
|
import { LabelSignPostComponent } from './label-signpost/label-signpost.component';
|
||||||
|
import { LabelFilterComponent } from './label-filter/label-filter.component';
|
||||||
|
|
||||||
export const LABEL_DIRECTIVES: Type<any>[] = [
|
export const LABEL_DIRECTIVES: Type<any>[] = [
|
||||||
LabelComponent,
|
LabelComponent,
|
||||||
LabelMarkerComponent,
|
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 {
|
.filter-div {
|
||||||
margin-left: 9px;
|
margin-left: 9px;
|
||||||
|
margin-right: 9px;
|
||||||
|
margin-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-items-container {
|
.label-items-container {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
<div class="trigger-item">
|
<div class="trigger-item">
|
||||||
<clr-signpost>
|
<clr-signpost>
|
||||||
<button class="btn btn-link" clrSignpostTrigger>
|
<button class="btn btn-link" clrSignpostTrigger>...</button>
|
||||||
...
|
|
||||||
</button>
|
|
||||||
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
|
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
|
||||||
<div *ngFor="let label of labels">
|
<div *ngFor="let label of labels">
|
||||||
<hbr-label-piece [label]="label" [labelWidth]="130"></hbr-label-piece>
|
<hbr-label-piece [label]="label" [labelWidth]="130"></hbr-label-piece>
|
||||||
|
@ -4,6 +4,7 @@ clr-signpost {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
vertical-align: super;
|
vertical-align: super;
|
||||||
|
line-height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
clr-signpost-content {
|
clr-signpost-content {
|
||||||
|
@ -104,4 +104,5 @@ export enum Roles {
|
|||||||
export enum ResourceType {
|
export enum ResourceType {
|
||||||
REPOSITORY = 1,
|
REPOSITORY = 1,
|
||||||
CHART_VERSION = 2,
|
CHART_VERSION = 2,
|
||||||
|
REPOSITORY_TAG = 3,
|
||||||
}
|
}
|
||||||
|
@ -26,26 +26,32 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="row flex-items-xs-right rightPos">
|
<div class="row flex-items-xs-right rightPos">
|
||||||
<div id="filterArea">
|
<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='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">
|
<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>
|
<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">
|
<div class="labelFilterPanel" *ngIf="!withAdmiral" [hidden]="!openLabelFilterPanel">
|
||||||
<a class="filterClose" (click)="closeFilter()">×</a>
|
<a class="filterClose" (click)="closeFilter()">×</a>
|
||||||
<label class="filterLabelHeader">{{'REPOSITORY.FILTER_BY_LABEL' | translate}}</label>
|
<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 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="padding-left:10px;">{{'LABEL.NO_LABELS' | translate }}</div>
|
||||||
<div [hidden]='!imageFilterLabels.length' style='max-height:300px;overflow-y: auto;'>
|
<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)">
|
<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>
|
<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>
|
<div class='labelDiv'>
|
||||||
|
<hbr-label-piece [label]="label.label" [labelWidth]="160"></hbr-label-piece>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="refresh-btn" (click)="refresh()">
|
||||||
<span class="refresh-btn" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></span>
|
<clr-icon shape="refresh"></clr-icon>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
@ -65,3 +65,8 @@
|
|||||||
width: 1px;
|
width: 1px;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#results {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 99%;
|
||||||
|
}
|
@ -21,8 +21,7 @@ export class ListChartVersionRoComponent implements OnInit {
|
|||||||
private projectService: ProjectService,
|
private projectService: ProjectService,
|
||||||
private router: Router) { }
|
private router: Router) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {}
|
||||||
}
|
|
||||||
|
|
||||||
getStatusString(chart: HelmChartVersion) {
|
getStatusString(chart: HelmChartVersion) {
|
||||||
if (chart.deprecated) {
|
if (chart.deprecated) {
|
||||||
|
@ -770,7 +770,7 @@
|
|||||||
"DELETE": "Delete",
|
"DELETE": "Delete",
|
||||||
"LABEL_NAME": "Label Name",
|
"LABEL_NAME": "Label Name",
|
||||||
"COLOR": "Color",
|
"COLOR": "Color",
|
||||||
"FILTER_Label_PLACEHOLDER": "Filter Labels",
|
"FILTER_LABEL_PLACEHOLDER": "Filter Labels",
|
||||||
"NO_LABELS": "No labels",
|
"NO_LABELS": "No labels",
|
||||||
"DELETION_TITLE_TARGET": "Confirm Label Deletion",
|
"DELETION_TITLE_TARGET": "Confirm Label Deletion",
|
||||||
"DELETION_SUMMARY_TARGET": "Do you want to delete {{param}}?",
|
"DELETION_SUMMARY_TARGET": "Do you want to delete {{param}}?",
|
||||||
|
@ -733,7 +733,7 @@
|
|||||||
"DELETE": "Delete",
|
"DELETE": "Delete",
|
||||||
"LABEL_NAME": "Label Name",
|
"LABEL_NAME": "Label Name",
|
||||||
"COLOR": "Color",
|
"COLOR": "Color",
|
||||||
"FILTER_Label_PLACEHOLDER": "Filter Labels",
|
"FILTER_LABEL_PLACEHOLDER": "Filter Labels",
|
||||||
"NO_LABELS": "No labels",
|
"NO_LABELS": "No labels",
|
||||||
"DELETION_TITLE_TARGET": "Confirm Label Deletion",
|
"DELETION_TITLE_TARGET": "Confirm Label Deletion",
|
||||||
"DELETION_SUMMARY_TARGET": "Do you want to delete {{param}}?",
|
"DELETION_SUMMARY_TARGET": "Do you want to delete {{param}}?",
|
||||||
|
@ -768,7 +768,7 @@
|
|||||||
"DELETE": "删除",
|
"DELETE": "删除",
|
||||||
"LABEL_NAME": "标签名字",
|
"LABEL_NAME": "标签名字",
|
||||||
"COLOR": "颜色",
|
"COLOR": "颜色",
|
||||||
"FILTER_Label_PLACEHOLDER": "过滤标签",
|
"FILTER_LABEL_PLACEHOLDER": "过滤标签",
|
||||||
"NO_LABELS": "无标签",
|
"NO_LABELS": "无标签",
|
||||||
"DELETION_TITLE_TARGET": "删除标签确认",
|
"DELETION_TITLE_TARGET": "删除标签确认",
|
||||||
"DELETION_SUMMARY_TARGET": "确认删除标签 {{param}}?",
|
"DELETION_SUMMARY_TARGET": "确认删除标签 {{param}}?",
|
||||||
|
Loading…
Reference in New Issue
Block a user