mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-22 23:51:27 +01:00
Add labels on chart summary page
Add labels on chart summary Signed-off-by: Qian Deng <dengq@vmware.com>
This commit is contained in:
parent
7ffa135432
commit
6eb9fa53a7
@ -130,6 +130,26 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 content-group">
|
||||
<div>
|
||||
<label>{{'HELM_CHART.LABELS' | translate }}</label>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="left">{{'HELM_CHART.LABELS' | translate }}</th>
|
||||
<th class="left">{{labels?.length}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="left">
|
||||
<hbr-label-piece *ngFor="let label of labels" [label]="label" [labelWidth]="90"> </hbr-label-piece>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -5,7 +5,7 @@ import {
|
||||
Input
|
||||
} from "@angular/core";
|
||||
|
||||
import { HelmChartMetaData, HelmChartSecurity } from "./../../service/interface";
|
||||
import { HelmChartMetaData, HelmChartSecurity, Label } from "./../../service/interface";
|
||||
import { downloadFile } from './../../utils';
|
||||
import { HelmChartService } from "../../service/index";
|
||||
import { ErrorHandler } from "./../../error-handler/error-handler";
|
||||
@ -24,6 +24,7 @@ export class ChartDetailSummaryComponent implements OnInit {
|
||||
@Input() chartName: string;
|
||||
@Input() chartVersion: string;
|
||||
@Input() readme: string;
|
||||
@Input() labels: Label[];
|
||||
|
||||
copiedCMD = '';
|
||||
addCMD: string;
|
||||
|
@ -28,7 +28,8 @@
|
||||
[projectName]="project.name"
|
||||
[chartVersion]="chartVersion"
|
||||
[security]="chartDetail.security"
|
||||
[readme]="chartDetail.files['README.md']"></hbr-chart-detail-summary>
|
||||
[readme]="chartDetail.files['README.md']"
|
||||
[labels]="chartDetail.labels"></hbr-chart-detail-summary>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
<clr-tab>
|
||||
|
@ -57,7 +57,8 @@
|
||||
[labels]="projectLabels"
|
||||
[projectName]="projectName"
|
||||
[resource]="selectedRows[0]"
|
||||
[resourceType]="resourceType">
|
||||
[resourceType]="resourceType"
|
||||
(changeEvt)="onLabelChange(selectedRows[0])">
|
||||
</hbr-resource-label-marker>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
@ -68,7 +69,7 @@
|
||||
|
||||
<clr-dg-column>{{'HELM_CHART.MAINTAINERS' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{'HELM_CHART.CREATED' | translate }}</clr-dg-column>
|
||||
<clr-dg-column style="width: 140px;">{{'REPOSITORY.LABELS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.LABELS' | translate}}</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-cell>
|
||||
@ -81,20 +82,9 @@
|
||||
<clr-dg-cell>{{ v.engine }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{ getMaintainerString(v.maintainers) }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{ v.created | date}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 140px;">
|
||||
<clr-dg-cell>
|
||||
<hbr-label-piece *ngIf="v.labels?.length" [label]="v.labels[0]"> </hbr-label-piece>
|
||||
<div class="signpost-item" [hidden]="v.labels?.length<=1">
|
||||
<div class="trigger-item">
|
||||
<clr-signpost>
|
||||
<button class="btn btn-link" clrSignpostTrigger>...</button>
|
||||
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
|
||||
<div>
|
||||
<hbr-label-piece *ngFor="let label of v.labels" [label]="label"></hbr-label-piece>
|
||||
</div>
|
||||
</clr-signpost-content>
|
||||
</clr-signpost>
|
||||
</div>
|
||||
</div>
|
||||
<hbr-resource-label-signpost *ngIf="v.labels?.length>1" [labels]="v.labels"></hbr-resource-label-signpost>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
|
@ -39,6 +39,10 @@ clr-dg-action-bar {
|
||||
}
|
||||
}
|
||||
|
||||
hbr-resource-label-signpost {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
margin-top: 21px;
|
||||
.card-header {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Label } from './../../service/interface';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
@ -22,6 +21,7 @@ import {
|
||||
HelmChartMaintainer,
|
||||
LabelService
|
||||
} from "./../../service/index";
|
||||
import { Label } from './../../service/interface';
|
||||
import { ErrorHandler } from "./../../error-handler/error-handler";
|
||||
import { toPromise, DEFAULT_PAGE_SIZE, downloadFile } from "../../utils";
|
||||
import { OperationService } from "./../../operation/operation.service";
|
||||
@ -61,7 +61,6 @@ export class ChartVersionComponent implements OnInit {
|
||||
|
||||
lastFilteredVersionName: string;
|
||||
chartVersions: HelmChartVersion[] = [];
|
||||
versionsCopy: HelmChartVersion[] = [];
|
||||
systemInfo: SystemInfo;
|
||||
selectedRows: HelmChartVersion[] = [];
|
||||
projectLabels: Label[] = [];
|
||||
@ -85,7 +84,6 @@ export class ChartVersionComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private translateService: TranslateService,
|
||||
private systemInfoService: SystemInfoService,
|
||||
private helmChartService: HelmChartService,
|
||||
private resrouceLabelService: LabelService,
|
||||
@ -116,7 +114,6 @@ export class ChartVersionComponent implements OnInit {
|
||||
this.resrouceLabelService.getProjectLabels(this.projectId).subscribe(
|
||||
(labels: Label[]) => {
|
||||
this.projectLabels = labels;
|
||||
console.log('chart version project labels', this.projectLabels);
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -126,6 +123,7 @@ export class ChartVersionComponent implements OnInit {
|
||||
this.helmChartService
|
||||
.getChartVersions(this.projectName, this.chartName)
|
||||
.pipe(finalize(() => {
|
||||
this.selectedRows = [];
|
||||
this.loading = false;
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 2000);
|
||||
@ -133,7 +131,6 @@ export class ChartVersionComponent implements OnInit {
|
||||
.subscribe(
|
||||
versions => {
|
||||
this.chartVersions = versions.filter(x => x.version.includes(this.lastFilteredVersionName));
|
||||
this.versionsCopy = versions.map(x => Object.assign({}, x));
|
||||
this.totalCount = versions.length;
|
||||
},
|
||||
err => {
|
||||
@ -314,4 +311,14 @@ export class ChartVersionComponent implements OnInit {
|
||||
return "HELM_CHART.ACTIVE";
|
||||
}
|
||||
}
|
||||
|
||||
onLabelChange(version: HelmChartVersion) {
|
||||
this.resrouceLabelService.getChartVersionLabels(this.projectName, this.chartName, version.version)
|
||||
.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
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';
|
||||
|
||||
export const LABEL_DIRECTIVES: Type<any>[] = [
|
||||
LabelComponent,
|
||||
LabelMarkerComponent
|
||||
LabelMarkerComponent,
|
||||
LabelSignPostComponent
|
||||
];
|
||||
|
@ -1,28 +1,11 @@
|
||||
|
||||
// Copyright (c) 2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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, Input, Output, OnInit, EventEmitter, ChangeDetectorRef, ViewChild, ElementRef } from '@angular/core';
|
||||
import { Observable, fromEvent } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { fromEvent, Subject } from 'rxjs';
|
||||
import { debounceTime, finalize } from 'rxjs/operators';
|
||||
|
||||
import { RepositoryItem, HelmChartVersion } from './../../service/interface';
|
||||
import {Label} from "../../service/interface";
|
||||
import { ResourceType } from '../../shared/shared.const';
|
||||
import { LabelService } from '../../service/label.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ErrorHandler } from '../../error-handler/error-handler';
|
||||
|
||||
@Component({
|
||||
@ -37,6 +20,7 @@ export class LabelMarkerComponent implements OnInit {
|
||||
@Input() projectName: string;
|
||||
@Input() resource: RepositoryItem | HelmChartVersion;
|
||||
@Input() resourceType: ResourceType;
|
||||
@Output() changeEvt = new EventEmitter<any>();
|
||||
|
||||
labelFilter = '';
|
||||
markedMap: Map<number, boolean> = new Map<number, boolean>();
|
||||
@ -45,6 +29,8 @@ export class LabelMarkerComponent implements OnInit {
|
||||
|
||||
loading = false;
|
||||
|
||||
labelChangeDebouncer: Subject<any> = new Subject();
|
||||
|
||||
@ViewChild('filterInput') filterInputRef: ElementRef;
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -53,12 +39,13 @@ export class LabelMarkerComponent implements OnInit {
|
||||
fromEvent(this.filterInputRef.nativeElement, 'keyup')
|
||||
.pipe(debounceTime(500))
|
||||
.subscribe(() => this.refresh());
|
||||
|
||||
this.labelChangeDebouncer.pipe(debounceTime(1000)).subscribe(() => this.changeEvt.emit());
|
||||
}
|
||||
|
||||
constructor(
|
||||
private labelService: LabelService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private translateService: TranslateService,
|
||||
private cdr: ChangeDetectorRef) {}
|
||||
|
||||
refresh() {
|
||||
@ -75,7 +62,6 @@ export class LabelMarkerComponent implements OnInit {
|
||||
}))
|
||||
.subscribe( chartVersionLabels => {
|
||||
for (let label of chartVersionLabels) {
|
||||
console.log('marked label', label);
|
||||
this.markedMap.set(label.id, true);
|
||||
}
|
||||
this.sortedLabels = this.getSortedLabels();
|
||||
@ -102,6 +88,7 @@ export class LabelMarkerComponent implements OnInit {
|
||||
() => {
|
||||
this.markedMap.set(label.id, true);
|
||||
this.refresh();
|
||||
this.labelChangeDebouncer.next();
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 5000);
|
||||
},
|
||||
@ -128,6 +115,7 @@ export class LabelMarkerComponent implements OnInit {
|
||||
() => {
|
||||
this.markedMap.set(label.id, false);
|
||||
this.refresh();
|
||||
this.labelChangeDebouncer.next();
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 5000);
|
||||
},
|
||||
|
@ -0,0 +1,12 @@
|
||||
<div class="trigger-item">
|
||||
<clr-signpost>
|
||||
<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>
|
||||
</div>
|
||||
</clr-signpost-content>
|
||||
</clr-signpost>
|
||||
</div>
|
@ -0,0 +1,21 @@
|
||||
clr-signpost {
|
||||
button {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
height: 24px;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
clr-signpost-content {
|
||||
::ng-deep {
|
||||
.signpost-content-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.signpost-content-body {
|
||||
padding-bottom: 6px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
|
||||
// Copyright (c) 2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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, Input, OnInit } from '@angular/core';
|
||||
|
||||
import {Label} from "../../service/interface";
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-resource-label-signpost',
|
||||
templateUrl: './label-signpost.component.html',
|
||||
styleUrls: ['./label-signpost.component.scss']
|
||||
})
|
||||
|
||||
export class LabelSignPostComponent implements OnInit {
|
||||
|
||||
@Input() labels: Label[] = [];
|
||||
|
||||
sortedLabels: Label[] = [];
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
}
|
159
src/portal/lib/src/service/resource-label.service.ts
Normal file
159
src/portal/lib/src/service/resource-label.service.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { Observable} from "rxjs";
|
||||
import { Label } from "./interface";
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { Http } from "@angular/http";
|
||||
import { IServiceConfig, SERVICE_CONFIG } from "../service.config";
|
||||
import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from "../utils";
|
||||
import { RequestQueryParams } from "./RequestQueryParams";
|
||||
|
||||
export abstract class LabelService {
|
||||
abstract getLabels(
|
||||
scope: string,
|
||||
projectId?: number,
|
||||
name?: string,
|
||||
queryParams?: RequestQueryParams
|
||||
): Observable<Label[]>;
|
||||
|
||||
abstract createLabel(
|
||||
label: Label
|
||||
): Observable<Label> | Promise<Label> | Label;
|
||||
|
||||
abstract getLabel(id: number): Observable<Label> | Promise<Label> | Label;
|
||||
|
||||
abstract updateLabel(
|
||||
id: number,
|
||||
param: Label
|
||||
): Observable<any> | Promise<any> | any;
|
||||
|
||||
abstract deleteLabel(id: number): Observable<any> | Promise<any> | any;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class LabelDefaultService extends LabelService {
|
||||
_labelUrl: string;
|
||||
|
||||
constructor(
|
||||
@Inject(SERVICE_CONFIG) config: IServiceConfig,
|
||||
private http: Http
|
||||
) {
|
||||
super();
|
||||
this._labelUrl = config.labelEndpoint
|
||||
? config.labelEndpoint
|
||||
: "/api/labels";
|
||||
}
|
||||
|
||||
getLabels(
|
||||
scope: string,
|
||||
projectId?: number,
|
||||
name?: string,
|
||||
queryParams?: RequestQueryParams
|
||||
): Observable<Label[]> | Promise<Label[]> {
|
||||
if (!queryParams) {
|
||||
queryParams = new RequestQueryParams();
|
||||
}
|
||||
if (scope) {
|
||||
queryParams.set("scope", scope);
|
||||
}
|
||||
if (projectId) {
|
||||
queryParams.set("project_id", "" + projectId);
|
||||
}
|
||||
if (name) {
|
||||
queryParams.set("name", "" + name);
|
||||
}
|
||||
return this.http
|
||||
.get(this._labelUrl, buildHttpRequestOptions(queryParams))
|
||||
.toPromise()
|
||||
.then(response => response.json())
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
getGLabels(
|
||||
name?: string,
|
||||
queryParams?: RequestQueryParams
|
||||
): Observable<Label[]> | Promise<Label[]> {
|
||||
if (!queryParams) {
|
||||
queryParams = new RequestQueryParams();
|
||||
}
|
||||
queryParams.set("scope", "g");
|
||||
|
||||
if (name) {
|
||||
queryParams.set("name", "" + name);
|
||||
}
|
||||
return this.http
|
||||
.get(this._labelUrl, buildHttpRequestOptions(queryParams))
|
||||
.toPromise()
|
||||
.then(response => response.json())
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
getPLabels(
|
||||
projectId: number,
|
||||
name?: string,
|
||||
queryParams?: RequestQueryParams
|
||||
): Observable<Label[]> | Promise<Label[]> {
|
||||
if (!queryParams) {
|
||||
queryParams = new RequestQueryParams();
|
||||
}
|
||||
queryParams.set("scope", "p");
|
||||
if (projectId) {
|
||||
queryParams.set("project_id", "" + projectId);
|
||||
}
|
||||
if (name) {
|
||||
queryParams.set("name", "" + name);
|
||||
}
|
||||
return this.http
|
||||
.get(this._labelUrl, buildHttpRequestOptions(queryParams))
|
||||
.toPromise()
|
||||
.then(response => response.json())
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
createLabel(label: Label): Observable<any> | Promise<any> | any {
|
||||
if (!label) {
|
||||
return Promise.reject("Invalid label.");
|
||||
}
|
||||
return this.http
|
||||
.post(this._labelUrl, JSON.stringify(label), HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
.then(response => response.status)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
getLabel(id: number): Observable<Label> | Promise<Label> | Label {
|
||||
if (!id || id <= 0) {
|
||||
return Promise.reject("Bad request argument.");
|
||||
}
|
||||
let reqUrl = `${this._labelUrl}/${id}`;
|
||||
return this.http
|
||||
.get(reqUrl)
|
||||
.toPromise()
|
||||
.then(response => response.json())
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
updateLabel(id: number, label: Label): Observable<any> | Promise<any> | any {
|
||||
if (!id || id <= 0) {
|
||||
return Promise.reject("Bad request argument.");
|
||||
}
|
||||
if (!label) {
|
||||
return Promise.reject("Invalid endpoint.");
|
||||
}
|
||||
let reqUrl = `${this._labelUrl}/${id}`;
|
||||
return this.http
|
||||
.put(reqUrl, JSON.stringify(label), HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
.then(response => response.status)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
deleteLabel(id: number): Observable<any> | Promise<any> | any {
|
||||
if (!id || id <= 0) {
|
||||
return Promise.reject("Bad request argument.");
|
||||
}
|
||||
let reqUrl = `${this._labelUrl}/${id}`;
|
||||
return this.http
|
||||
.delete(reqUrl)
|
||||
.toPromise()
|
||||
.then(response => response.status)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
}
|
@ -528,6 +528,7 @@
|
||||
"PROV_FILE": "Prov File",
|
||||
"READY": "Ready",
|
||||
"NOT_READY": "Not Ready",
|
||||
"LABELS": "labels",
|
||||
"STATUS": "Status"
|
||||
},
|
||||
"ALERT": {
|
||||
|
@ -527,6 +527,7 @@
|
||||
"PROV_FILE": "Prov File",
|
||||
"READY": "Ready",
|
||||
"NOT_READY": "Not Ready",
|
||||
"LABELS": "labels",
|
||||
"STATUS": "Status"
|
||||
},
|
||||
"ALERT": {
|
||||
|
@ -503,6 +503,7 @@
|
||||
"PROV_FILE": "Prov File",
|
||||
"READY": "Ready",
|
||||
"NOT_READY": "Not Ready",
|
||||
"LABELS": "labels",
|
||||
"STATUS": "Status"
|
||||
},
|
||||
"ALERT": {
|
||||
|
@ -527,6 +527,7 @@
|
||||
"PROV_FILE": "Prov 文件",
|
||||
"READY": "就绪",
|
||||
"NOT_READY": "未就绪",
|
||||
"LABELS": "标签",
|
||||
"STATUS": "状态"
|
||||
},
|
||||
"ALERT": {
|
||||
|
Loading…
Reference in New Issue
Block a user