mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-27 11:07:46 +01:00
Merge pull request #5423 from ninjadq/integrate_chart_view
Feature of helm chart UI
This commit is contained in:
commit
8b7706d191
2
.gitignore
vendored
2
.gitignore
vendored
@ -52,5 +52,3 @@ src/ui_ng/aot/**/*.json
|
||||
**/aot
|
||||
**/dist
|
||||
**/.bin
|
||||
package-lock.json
|
||||
src/ui_ng/package-lock.json
|
||||
|
@ -79,7 +79,7 @@ script:
|
||||
- sudo mkdir -p /harbor
|
||||
- sudo mv ./VERSION /harbor/UIVERSION
|
||||
- sudo service postgresql stop
|
||||
- sudo make run_clarity_ut CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0
|
||||
- sudo make run_clarity_ut CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.6.0
|
||||
- cat ./src/ui_ng/npm-ut-test-results
|
||||
- sudo ./tests/testprepare.sh
|
||||
- sudo make -f make/photon/Makefile _build_db _build_registry -e VERSIONTAG=dev -e CLAIRDBVERSION=dev -e REGISTRYVERSION=v2.6.2
|
||||
@ -105,7 +105,7 @@ script:
|
||||
- sudo rm -rf /data/config/*
|
||||
- sudo rm -rf /data/database/*
|
||||
- ls /data/cert
|
||||
- sudo make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0 NOTARYFLAG=true CLAIRFLAG=true
|
||||
- sudo make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.6.0 NOTARYFLAG=true CLAIRFLAG=true
|
||||
- sleep 10
|
||||
- docker ps
|
||||
- ./tests/validatecontainers.sh
|
||||
|
@ -50,19 +50,19 @@ You can compile the code by one of the three approaches:
|
||||
* Build, install and bring up Harbor without Notary:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.6.0
|
||||
```
|
||||
|
||||
* Build, install and bring up Harbor with Notary:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0 NOTARYFLAG=true
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.6.0 NOTARYFLAG=true
|
||||
```
|
||||
|
||||
* Build, install and bring up Harbor with Clair:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0 CLAIRFLAG=true
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.6.0 CLAIRFLAG=true
|
||||
```
|
||||
|
||||
#### II. Compile code with your own Golang environment, then build Harbor
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:7.5.0
|
||||
FROM node:10.7.0
|
||||
|
||||
RUN mkdir -p /harbor_resources
|
||||
RUN mkdir -p /harbor_src
|
||||
@ -7,14 +7,26 @@ COPY src/ui_ng/package.json /harbor_resources
|
||||
COPY make/dev/nodeclarity/entrypoint.sh /
|
||||
|
||||
# Install Chrome
|
||||
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
|
||||
RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | tee /etc/apt/sources.list.d/google-chrome.list
|
||||
RUN apt-get update && apt-get -y install google-chrome-stable
|
||||
RUN apt-get update && apt-get install -y \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
hicolor-icon-theme \
|
||||
libcanberra-gtk* \
|
||||
libgl1-mesa-dri \
|
||||
libgl1-mesa-glx \
|
||||
libpango1.0-0 \
|
||||
libpulse0 \
|
||||
libv4l-0 \
|
||||
--no-install-recommends
|
||||
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
RUN dpkg -i google-chrome-stable_current_amd64.deb; apt-get -fy install
|
||||
RUN rm google-chrome-stable_current_amd64.deb
|
||||
|
||||
# Install npm package
|
||||
WORKDIR /harbor_resources
|
||||
|
||||
RUN npm __proxy__ install -g @angular/cli && \
|
||||
npm __proxy__ install && \
|
||||
RUN npm __proxy__ install && \
|
||||
chmod u+x /entrypoint.sh
|
||||
VOLUME ["/harbor_src"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"project": {
|
||||
"version": "1.2.0",
|
||||
"version": "1.6.0",
|
||||
"name": "Harbor"
|
||||
},
|
||||
"apps": [{
|
||||
@ -19,6 +19,7 @@
|
||||
"styles": [
|
||||
"../node_modules/clarity-icons/clarity-icons.min.css",
|
||||
"../node_modules/clarity-ui/clarity-ui.min.css",
|
||||
"../node_modules/prismjs/themes/prism-solarizedlight.css",
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
@ -26,7 +27,10 @@
|
||||
"../node_modules/mutationobserver-shim/dist/mutationobserver.min.js",
|
||||
"../node_modules/@webcomponents/custom-elements/custom-elements.min.js",
|
||||
"../node_modules/clarity-icons/clarity-icons.min.js",
|
||||
"../node_modules/web-animations-js/web-animations.min.js"
|
||||
"../node_modules/web-animations-js/web-animations.min.js",
|
||||
"../node_modules/marked/lib/marked.js",
|
||||
"../node_modules/prismjs/prism.js",
|
||||
"../node_modules/prismjs/components/prism-yaml.min.js"
|
||||
],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
|
@ -26,6 +26,15 @@ fi
|
||||
cat ./package.json
|
||||
npm install
|
||||
|
||||
## Build harbor-ui and link it
|
||||
rm -rf /harbor_src/ui_ng/lib/dist
|
||||
npm run build:lib
|
||||
chmod -R +xr /harbor_src/ui_ng/lib/dist
|
||||
cd /harbor_src/ui_ng/lib/dist
|
||||
npm link
|
||||
cd /harbor_src/ui_ng
|
||||
npm link harbor-ui
|
||||
|
||||
./node_modules/.bin/ngc -p tsconfig-aot.json
|
||||
sed -i 's/* as//g' src/app/shared/gauge/gauge.component.js
|
||||
./node_modules/.bin/rollup -c rollup-config.js
|
||||
@ -44,3 +53,8 @@ cp ./node_modules/@webcomponents/custom-elements/custom-elements.min.js ../ui/st
|
||||
cp ./node_modules/clarity-icons/clarity-icons.min.js ../ui/static/
|
||||
cp ./node_modules/clarity-ui/clarity-ui.min.css ../ui/static/
|
||||
cp -r ./node_modules/clarity-icons/shapes/ ../ui/static/
|
||||
|
||||
cp ./node_modules/prismjs/themes/prism-solarizedlight.css ../ui/static/
|
||||
cp ./node_modules/marked/lib/marked.js ../ui/static/
|
||||
cp ./node_modules/prismjs/prism.js ../ui/static/
|
||||
cp ./node_modules/prismjs/components/prism-yaml.min.js ../ui/static/
|
@ -15,13 +15,18 @@
|
||||
Loading...
|
||||
</div>
|
||||
</harbor-app>
|
||||
|
||||
<link rel="stylesheet" href="/static/clarity-ui.min.css">
|
||||
<link rel="stylesheet" href="/static/clarity-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/prism-solarizedlight.css">
|
||||
<link rel="stylesheet" href="/static/styles.css">
|
||||
|
||||
<script src="/static/mutationobserver.min.js"></script>
|
||||
<script src="/static/custom-elements.min.js"></script>
|
||||
<script src="/static/clarity-icons.min.js"></script>
|
||||
<script src="/static/marked.js"></script>
|
||||
<script src="/static//prism.js"></script>
|
||||
<script src="/static/prism-yaml.min.js"></script>
|
||||
<script src="/static/build.min.js"></script>
|
||||
</body>
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
"styles": [
|
||||
"../node_modules/clarity-icons/clarity-icons.min.css",
|
||||
"../node_modules/clarity-ui/clarity-ui.min.css",
|
||||
"../node_modules/prismjs/themes/prism-solarizedlight.css",
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
@ -27,7 +28,10 @@
|
||||
"../node_modules/mutationobserver-shim/dist/mutationobserver.min.js",
|
||||
"../node_modules/@webcomponents/custom-elements/custom-elements.min.js",
|
||||
"../node_modules/clarity-icons/clarity-icons.min.js",
|
||||
"../node_modules/web-animations-js/web-animations.min.js"
|
||||
"../node_modules/web-animations-js/web-animations.min.js",
|
||||
"../node_modules/marked/lib/marked.js",
|
||||
"../node_modules/prismjs/prism.js",
|
||||
"../node_modules/prismjs/components/prism-yaml.min.js"
|
||||
],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
|
@ -4,7 +4,8 @@
|
||||
"entryFile": "index.ts",
|
||||
"externals": {
|
||||
"@ngx-translate/core": "ngx-translate-core",
|
||||
"@ngx-translate/core/index": "ngx-translate-core"
|
||||
"@ngx-translate/core/index": "ngx-translate-core",
|
||||
"ngx-markdown": "ngx-markdown"
|
||||
}
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
"ngx-markdown": "^1.5.1",
|
||||
"rxjs": "^5.0.1",
|
||||
"ts-helpers": "^1.1.1",
|
||||
"web-animations-js": "^2.2.1",
|
||||
|
@ -42,10 +42,11 @@ export class ConfirmationDialogComponent {
|
||||
|
||||
open(msg: ConfirmationMessage): void {
|
||||
this.dialogTitle = msg.title;
|
||||
this.dialogContent = msg.message;
|
||||
this.message = msg;
|
||||
this.translate.get(this.dialogTitle).subscribe((res: string) => this.dialogTitle = res);
|
||||
this.translate.get(this.dialogContent, { 'param': msg.param }).subscribe((res: string) => this.dialogContent = res);
|
||||
this.translate.get(msg.message, { 'param': msg.param }).subscribe((res: string) => {
|
||||
this.dialogContent = res;
|
||||
});
|
||||
// Open dialog
|
||||
this.buttons = msg.buttons;
|
||||
this.opened = true;
|
||||
|
@ -25,10 +25,10 @@ import { PROJECT_POLICY_CONFIG_DIRECTIVES } from './project-policy-config/index'
|
||||
import { HBR_GRIDVIEW_DIRECTIVES } from './gridview/index';
|
||||
import { REPOSITORY_GRIDVIEW_DIRECTIVES } from './repository-gridview/index';
|
||||
import { OPERATION_DIRECTIVES } from './operation/index';
|
||||
import {LABEL_DIRECTIVES} from "./label/index";
|
||||
import {CREATE_EDIT_LABEL_DIRECTIVES} from "./create-edit-label/index";
|
||||
import {LABEL_PIECE_DIRECTIVES} from "./label-piece/index";
|
||||
|
||||
import { LABEL_DIRECTIVES } from "./label/index";
|
||||
import { CREATE_EDIT_LABEL_DIRECTIVES } from "./create-edit-label/index";
|
||||
import { LABEL_PIECE_DIRECTIVES } from "./label-piece/index";
|
||||
import { HELMCHART_DIRECTIVE } from "./helm-chart/index";
|
||||
import {
|
||||
SystemInfoService,
|
||||
SystemInfoDefaultService,
|
||||
@ -52,6 +52,8 @@ import {
|
||||
ProjectDefaultService,
|
||||
LabelService,
|
||||
LabelDefaultService,
|
||||
HelmChartService,
|
||||
HelmChartDefaultService
|
||||
} from './service/index';
|
||||
import {
|
||||
ErrorHandler,
|
||||
@ -90,7 +92,9 @@ export const DefaultServiceConfig: IServiceConfig = {
|
||||
localI18nMessageVariableMap: {},
|
||||
configurationEndpoint: "/api/configurations",
|
||||
scanJobEndpoint: "/api/jobs/scan",
|
||||
labelEndpoint: "/api/labels"
|
||||
labelEndpoint: "/api/labels",
|
||||
helmChartEndpoint: "/api/chartrepo",
|
||||
downloadChartEndpoint: "/chartrepo"
|
||||
};
|
||||
|
||||
/**
|
||||
@ -138,6 +142,9 @@ export interface HarborModuleConfig {
|
||||
|
||||
// Service implementation for label
|
||||
labelService?: Provider;
|
||||
|
||||
// Service implementation for helmchart
|
||||
helmChartService?: Provider;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,7 +191,8 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
||||
LABEL_PIECE_DIRECTIVES,
|
||||
HBR_GRIDVIEW_DIRECTIVES,
|
||||
REPOSITORY_GRIDVIEW_DIRECTIVES,
|
||||
OPERATION_DIRECTIVES
|
||||
OPERATION_DIRECTIVES,
|
||||
HELMCHART_DIRECTIVE
|
||||
],
|
||||
exports: [
|
||||
LOG_DIRECTIVES,
|
||||
@ -210,7 +218,8 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
||||
LABEL_PIECE_DIRECTIVES,
|
||||
HBR_GRIDVIEW_DIRECTIVES,
|
||||
REPOSITORY_GRIDVIEW_DIRECTIVES,
|
||||
OPERATION_DIRECTIVES
|
||||
OPERATION_DIRECTIVES,
|
||||
HELMCHART_DIRECTIVE
|
||||
],
|
||||
providers: []
|
||||
})
|
||||
@ -233,6 +242,7 @@ export class HarborLibraryModule {
|
||||
config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService },
|
||||
config.projectPolicyService || { provide: ProjectService, useClass: ProjectDefaultService },
|
||||
config.labelService || {provide: LabelService, useClass: LabelDefaultService},
|
||||
config.helmChartService || {provide: HelmChartService, useClass: HelmChartDefaultService},
|
||||
// Do initializing
|
||||
TranslateServiceInitializer,
|
||||
{
|
||||
@ -264,6 +274,7 @@ export class HarborLibraryModule {
|
||||
config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService },
|
||||
config.projectPolicyService || { provide: ProjectService, useClass: ProjectDefaultService },
|
||||
config.labelService || {provide: LabelService, useClass: LabelDefaultService},
|
||||
config.helmChartService || {provide: HelmChartService, useClass: HelmChartDefaultService},
|
||||
ChannelService,
|
||||
OperationService
|
||||
]
|
||||
|
@ -0,0 +1,20 @@
|
||||
<div class="row flex-items-xs-center dep-container">
|
||||
<div class="col-md-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="left">{{'HELM_CHART.NAME' | translate}}</th>
|
||||
<th class="left">{{'HELM_CHART.VERSION' | translate}}</th>
|
||||
<th class="left">{{'HELM_CHART.REPO' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let dep of dependencies">
|
||||
<td class="left">{{dep.name}}</td>
|
||||
<td class="left">{{dep.version}}</td>
|
||||
<td class="left"><a href="{{dep.repository}}">{{dep.repository}}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
.dep-container {
|
||||
margin-top: 30px;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Input,
|
||||
ChangeDetectionStrategy
|
||||
} from "@angular/core";
|
||||
|
||||
import { HelmChartDependency } from "./../../service/interface";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-chart-detail-dependency",
|
||||
templateUrl: "./chart-detail-dependency.component.html",
|
||||
styleUrls: ["./chart-detail-dependency.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChartDetailDependencyComponent implements OnInit {
|
||||
@Input() dependencies: HelmChartDependency;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>{{summary.description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-8 md-container">
|
||||
<div *ngIf="readme" class="md-div" [innerHTML]="readme | markdown"></div>
|
||||
<div *ngIf="!readme">{{'HELM_CHART.NO_README' | translate}}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 summary-container">
|
||||
<div class="col-md-12 content-group">
|
||||
<div>
|
||||
<label>{{'HELM_CHART.OVERVIEW' | translate }}</label>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="left">{{'HELM_CHART.HOME' | translate }}</td>
|
||||
<td class="left">
|
||||
<a href="{{summary.home}}">{{summary.home}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">{{'HELM_CHART.SRC_REPO' | translate }}</td>
|
||||
<td class="left">
|
||||
<a href="{{summary.sources}}">{{summary.sources}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">{{'HELM_CHART.CREATED' | translate }}</td>
|
||||
<td class="left">{{summary.created | date}}</td>
|
||||
</tr>
|
||||
<tr *ngFor="let maintainer of summary.maintainers; let i = index">
|
||||
<td class="left" *ngIf="i === 0">{{'HELM_CHART.MAINTAINERS' | translate }}</td>
|
||||
<td class="left" *ngIf="i !== 0"></td>
|
||||
<td class="left">
|
||||
<a href="mailto:{{maintainer.email}}">{{maintainer.name}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">{{'HELM_CHART.VERSION' | translate }}</td>
|
||||
<td class="left">{{ summary.appVersion }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-12 content-group">
|
||||
<div>
|
||||
<label>{{'HELM_CHART.ADD_REPO' | translate }}</label>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="left">{{addCMD}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-12 content-group">
|
||||
<div>
|
||||
<label>{{'HELM_CHART.INSTALL_CHART' | translate }}</label>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="left">{{installCMD}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-12 content-group">
|
||||
<div>
|
||||
<label>{{'HELM_CHART.SECURITY' | translate }}</label>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="left">{{'HELM_CHART.SIGNED' | translate }}</td>
|
||||
<div *ngIf="security?.signature?.signed;then signed_content else unsignd_content"></div>
|
||||
<ng-template #signed_content>
|
||||
<td class="left">
|
||||
<span class="content-icon">
|
||||
<clr-icon shape="shield-check" class="is-success"></clr-icon>
|
||||
</span> {{'HELM_CHART.SIGNED' | translate }}</td>
|
||||
</ng-template>
|
||||
<ng-template #unsignd_content>
|
||||
<td class="left">
|
||||
<span class="content-icon">
|
||||
<clr-icon shape="shield-x" class="is-error"></clr-icon>
|
||||
</span> {{'HELM_CHART.UNSIGNED' | translate }}</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -0,0 +1,22 @@
|
||||
.md-container {
|
||||
margin-top: 15px;
|
||||
border: solid 1px #DDDDDD;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.summary-container {
|
||||
margin-top: 15px;
|
||||
|
||||
table {
|
||||
background-color: #F2F2F2;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.content-group {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.content-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
Input
|
||||
} from "@angular/core";
|
||||
|
||||
import { HelmChartMetaData, HelmChartSecurity } from "./../../service/interface";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-chart-detail-summary",
|
||||
templateUrl: "./chart-detail-summary.component.html",
|
||||
styleUrls: ["./chart-detail-summary.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChartDetailSummaryComponent implements OnInit {
|
||||
@Input() summary: HelmChartMetaData;
|
||||
@Input() security: HelmChartSecurity;
|
||||
@Input() repoURL: string;
|
||||
@Input() projectName: string;
|
||||
@Input() chartName: string;
|
||||
@Input() chartVersion: string;
|
||||
@Input() readme: string;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
public get addCMD() {
|
||||
return `helm repo add REPO_NAME ${this.repoURL}/chartrepo/${this.projectName}`;
|
||||
}
|
||||
|
||||
public get installCMD() {
|
||||
return `helm install --version ${this.chartVersion} REPO_NAME/${this.chartName}`;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<div class="row flex-items-xs-right">
|
||||
<div class="swichy-container">
|
||||
<span class="card-btn" (click)="showYamlFile(false)" (mouseenter)="mouseEnter('value') " (mouseleave)="mouseLeave('value')">
|
||||
<clr-icon size="36" shape="view-list" title='list values'
|
||||
[ngClass]="{'is-highlight': isValueMode || isHovering('value') }" ></clr-icon>
|
||||
</span>
|
||||
<span class="list-btn" (click)="showYamlFile(true)" (mouseenter)="mouseEnter('yaml') " (mouseleave)="mouseLeave('yaml')">
|
||||
<clr-icon size="36" shape="file" title="yaml file"
|
||||
[ngClass]="{'is-highlight': !isValueMode || isHovering('yaml') }"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row value-container">
|
||||
<div class="col-xs-8" *ngIf="valueMode">
|
||||
<div>
|
||||
<label>{{'HELM_CHART.SHOW_KV' | translate }}</label>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr *ngFor="let key of objKeys(values)">
|
||||
<td class="left">{{key}}</td>
|
||||
<td class="left">{{values[key]}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-xs-8" *ngIf="!valueMode">
|
||||
<div>
|
||||
<label>{{'HELM_CHART.SHOW_YAML' | translate }}</label>
|
||||
</div>
|
||||
<div class="yaml-container" [innerHTML]="yaml | language : 'yaml' | markdown"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,15 @@
|
||||
.value-container {
|
||||
::ng-deep pre {
|
||||
min-height: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
.swichy-container {
|
||||
margin-top: 3px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
pre {
|
||||
max-height: max-content;
|
||||
padding-left: 21px;
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy
|
||||
} from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-chart-detail-value",
|
||||
templateUrl: "./chart-detail-value.component.html",
|
||||
styleUrls: ["./chart-detail-value.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChartDetailValueComponent implements OnInit {
|
||||
@Input() values;
|
||||
@Input() yaml;
|
||||
|
||||
// Default set to yaml file
|
||||
valueMode = false;
|
||||
valueHover = false;
|
||||
yamlHover = true;
|
||||
|
||||
objKeys = Object.keys;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
public get isValueMode() {
|
||||
return this.valueMode;
|
||||
}
|
||||
|
||||
isHovering(view: string) {
|
||||
if (view === 'value') {
|
||||
return this.valueHover ? true : false;
|
||||
} else {
|
||||
return this.yamlHover ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
showYamlFile(showYaml: boolean) {
|
||||
this.valueMode = !showYaml;
|
||||
}
|
||||
|
||||
mouseEnter(mode: string) {
|
||||
if (mode === "value") {
|
||||
this.valueHover = true;
|
||||
} else {
|
||||
this.yamlHover = true;
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeave(mode: string) {
|
||||
if (mode === "value") {
|
||||
this.valueHover = false;
|
||||
} else {
|
||||
this.yamlHover = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<div>
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="col-xs-4">
|
||||
<div class="title-container">
|
||||
<div class="chart-name">
|
||||
{{chartNameWithVersion | translate}}
|
||||
</div>
|
||||
<div>
|
||||
{{roleName | translate}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-1">
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
(click)="downloadChart()">{{'HELM_CHART.DOWNLOAD' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-loading" *ngIf="loading">
|
||||
<span class="spinner">
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="!loading && isChartExist">
|
||||
<clr-tabs>
|
||||
<clr-tab>
|
||||
<button clrTabLink id="summary-link">{{'HELM_CHART.SUMMARY' | translate}}</button>
|
||||
<clr-tab-content id="summary-content" *clrIfActive>
|
||||
<hbr-chart-detail-summary
|
||||
[summary]="chartDetail.metadata"
|
||||
[chartName]="chartName"
|
||||
[repoURL]="repoURL"
|
||||
[projectName]="project.name"
|
||||
[chartVersion]="chartVersion"
|
||||
[security]="chartDetail.security"
|
||||
[readme]="chartDetail.files['README.md']"></hbr-chart-detail-summary>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
<clr-tab>
|
||||
<button clrTabLink id="depend-link">{{'HELM_CHART.DEPENDENCIES' | translate}}</button>
|
||||
<clr-tab-content id="depend-content">
|
||||
<hbr-chart-detail-dependency [dependencies]='chartDetail.dependencies'></hbr-chart-detail-dependency>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
<clr-tab>
|
||||
<button clrTabLink id="value-link">{{'HELM_CHART.VALUES' | translate}}</button>
|
||||
<clr-tab-content id="value-content">
|
||||
<hbr-chart-detail-value
|
||||
[values]="chartDetail.values"
|
||||
[yaml]="chartDetail.files['values.yaml']"></hbr-chart-detail-value>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
</clr-tabs>
|
||||
</div>
|
||||
<div *ngIf="!loading && !isChartExist">
|
||||
<h6>{{'HELM_CHART.NO_DETAIL' | translate }}</h6>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -0,0 +1,20 @@
|
||||
.title-container {
|
||||
display: flex;
|
||||
.chart-name {
|
||||
border-right: 1px solid gray;
|
||||
font-size: 27px;
|
||||
font-weight: normal;
|
||||
padding-right: 9px;
|
||||
margin-right: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
width: 108px !important;
|
||||
height: 108px !important;
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
import { Project } from "./../../project-policy-config/project";
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
ChangeDetectorRef
|
||||
} from "@angular/core";
|
||||
|
||||
import { downloadFile, toPromise } from "../../utils";
|
||||
import { SystemInfoService, HelmChartService } from "../../service/index";
|
||||
import { HelmChartDetail, SystemInfo } from "./../../service/interface";
|
||||
import { ErrorHandler } from "./../../error-handler/error-handler";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-chart-detail",
|
||||
templateUrl: "./chart-detail.component.html",
|
||||
styleUrls: ["./chart-detail.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChartDetailComponent implements OnInit {
|
||||
@Input() projectId: number;
|
||||
@Input() project: Project;
|
||||
@Input() chartName: string;
|
||||
@Input() chartVersion: string;
|
||||
@Input() roleName: string;
|
||||
@Input() hasSignedIn: boolean;
|
||||
@Input() hasProjectAdminRole: boolean;
|
||||
|
||||
loading = true;
|
||||
isMember = false;
|
||||
chartDetail: HelmChartDetail;
|
||||
systemInfo: SystemInfo;
|
||||
|
||||
repoURL = "";
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private systemInfoService: SystemInfoService,
|
||||
private helmChartService: HelmChartService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
|
||||
.then(systemInfo => {
|
||||
let scheme = 'http://';
|
||||
this.systemInfo = systemInfo;
|
||||
if (this.systemInfo.has_ca_root) {
|
||||
scheme = 'https://';
|
||||
}
|
||||
this.repoURL = `${scheme}${this.systemInfo.registry_url}`;
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
this.refresh();
|
||||
}
|
||||
public get chartNameWithVersion() {
|
||||
return `${this.chartName}:${this.chartVersion}`;
|
||||
}
|
||||
|
||||
public get isChartExist() {
|
||||
return this.chartDetail ? true : false;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.loading = true;
|
||||
this.helmChartService
|
||||
.getChartDetail(this.project.name, this.chartName, this.chartVersion)
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 2000);
|
||||
})
|
||||
.subscribe(
|
||||
chartDetail => {
|
||||
this.chartDetail = chartDetail;
|
||||
},
|
||||
err => {
|
||||
this.errorHandler.error(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
downloadChart() {
|
||||
if (!this.chartDetail ||
|
||||
!this.chartDetail.metadata ||
|
||||
!this.chartDetail.metadata.urls ||
|
||||
this.chartDetail.metadata.urls.length < 1) {
|
||||
return;
|
||||
}
|
||||
let filename = this.chartDetail.metadata.urls[0];
|
||||
|
||||
this.helmChartService.downloadChart(this.project.name, filename).subscribe(
|
||||
res => {
|
||||
downloadFile(res);
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
100
src/ui_ng/lib/src/helm-chart/helm-chart.component.html
Normal file
100
src/ui_ng/lib/src/helm-chart/helm-chart.component.html
Normal file
@ -0,0 +1,100 @@
|
||||
<div>
|
||||
<div class="row chart-tool">
|
||||
<div class="toolbar">
|
||||
<div class="row flex-items-xs-right option-right rightPos">
|
||||
<div class="flex-xs-middle">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'HELM_CHART.FILTER_FOR_CHARTS' | translate}}" [currentValue]="lastFilteredChartName"></hbr-filter>
|
||||
<span class="card-btn" (click)="showCard(true)" (mouseenter)="mouseEnter('card') " (mouseleave)="mouseLeave('card')">
|
||||
<clr-icon [ngClass]="{'is-highlight': isCardView || isHovering('card') }" shape="view-cards"></clr-icon>
|
||||
</span>
|
||||
<span class="list-btn" (click)="showCard(false)" (mouseenter)="mouseEnter('list') " (mouseleave)="mouseLeave('list')">
|
||||
<clr-icon [ngClass]="{'is-highlight': !isCardView || isHovering('list') }" shape="view-list"></clr-icon>
|
||||
</span>
|
||||
<span class="filter-divider"></span>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div *ngIf="!isCardView" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid (clrDgRefresh)="refresh($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRows">
|
||||
<clr-dg-action-bar>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!hasProjectAdminRole" (click)="onChartUpload($event)">
|
||||
<clr-icon shape="upload" size="16"></clr-icon> {{'HELM_CHART.UPLOAD' | translate}}
|
||||
</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'HELM_CHART.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'HELM_CHART.VERSIONS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'HELM_CHART.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'HELM_CHART.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let chart of charts" [clrDgItem]="chart">
|
||||
<clr-dg-cell>
|
||||
<a href="javascript:void(0)" (click)="onChartClick(chart)">{{ chart.name }}</a>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{ chart.total_versions }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{ chart.created | date }}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize">
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'HELM_CHART.OF' | translate}} {{pagination.totalItems}} {{'HELM_CHART.ITEMS'
|
||||
| translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isCardView" class="row card-container">
|
||||
<div *ngFor="let item of charts;" class="col-lg-3 col-md-4 col-sm-6">
|
||||
<a let i=index; class="card clickable" (click)="onChartClick(item)">
|
||||
<div class="card-header">
|
||||
<div class="card-media-block wrap">
|
||||
<div class="card-media-description">
|
||||
<span class="card-media-title">{{item.name}}</span>
|
||||
<p class="card-media-text">{{item.home}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
<div class="form-group">
|
||||
<label>{{'HELM_CHART.VERSIONS' | translate}}</label>
|
||||
<div>{{item.total_versions}}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{'HELM_CHART.CREATED' | translate}}</label>
|
||||
<div>{{item.Created | date}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div *ngIf="loading" [ngClass]="{'central-block-loading': isFirstPage, 'central-block-loading-more': !isFirstPage}">
|
||||
<span class="vertical-helper"></span>
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
<clr-modal [(clrModalOpen)]="isUploadModalOpen" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">{{'HELM_CHART.UPLOAD_TITLE' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<form #chartUploadForm="ngForm" enctype="multipart/form-data" (ngSubmit)="upload()">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="chart"> {{'HELM_CHART.CHART_FILE' | translate}} </label>
|
||||
<input type="file" id="chart" name="chart" ngModel (change)="onChartFileChangeEvent($event)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="prov"> {{'HELM_CHART.CHART_PROV' | translate}} </label>
|
||||
<input type="file" id="prov" name="prov" ngModel (change)="onProvFileChangeEvent($event)">
|
||||
</div>
|
||||
</section>
|
||||
<button type="submit" class="btn btn-secondary" [disabled]="isUploading">
|
||||
<span *ngIf="!isUploading">{{'HELM_CHART.UPLOAD' | translate}}</span>
|
||||
<span *ngIf="isUploading" class="spinner spinner-inline">
|
||||
Loading...
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</div>
|
68
src/ui_ng/lib/src/helm-chart/helm-chart.component.scss
Normal file
68
src/ui_ng/lib/src/helm-chart/helm-chart.component.scss
Normal file
@ -0,0 +1,68 @@
|
||||
.chart-tool {
|
||||
position: relative;
|
||||
.toolbar {
|
||||
overflow: hidden;
|
||||
.rightPos {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
.filter-divider {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 2px;
|
||||
background-color: #cccccc;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
position: relative;
|
||||
top: 9px;
|
||||
margin-right: 6px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-container {
|
||||
margin-top: 21px;
|
||||
.card-header {
|
||||
.card-media-block {
|
||||
.card-media-description {
|
||||
height: 45px;
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.card-media-title {
|
||||
overflow: hidden;
|
||||
height: 24px;
|
||||
}
|
||||
.card-media-text {
|
||||
overflow: hidden;
|
||||
height: 21px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.card-block {
|
||||
margin-top: 24px;
|
||||
min-height: 100px;
|
||||
.form-group {
|
||||
display: flex;
|
||||
label {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-helper {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
vertical-align: middle;
|
||||
}
|
171
src/ui_ng/lib/src/helm-chart/helm-chart.component.ts
Normal file
171
src/ui_ng/lib/src/helm-chart/helm-chart.component.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ChangeDetectorRef
|
||||
} from "@angular/core";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { State } from "clarity-angular";
|
||||
|
||||
import { SystemInfo, SystemInfoService, HelmChartItem } from "../service/index";
|
||||
import { ErrorHandler } from "../error-handler/error-handler";
|
||||
import { toPromise, DEFAULT_PAGE_SIZE } from "../utils";
|
||||
import { HelmChartService } from "../service/helm-chart.service";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-helm-chart",
|
||||
templateUrl: "./helm-chart.component.html",
|
||||
styleUrls: ["./helm-chart.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HelmChartComponent implements OnInit {
|
||||
signedCon: { [key: string]: any | string[] } = {};
|
||||
@Input() projectId: number;
|
||||
@Input() projectName = "unknown";
|
||||
@Input() urlPrefix: string;
|
||||
@Input() hasSignedIn: boolean;
|
||||
@Input() hasProjectAdminRole: boolean;
|
||||
@Output() chartClickEvt = new EventEmitter<any>();
|
||||
@Output() chartDownloadEve = new EventEmitter<string>();
|
||||
|
||||
lastFilteredChartName: string;
|
||||
charts: HelmChartItem[] = [];
|
||||
chartsCopy: HelmChartItem[] = [];
|
||||
systemInfo: SystemInfo;
|
||||
selectedRows: HelmChartItem[] = [];
|
||||
loading = true;
|
||||
|
||||
// For Upload
|
||||
isUploading = false;
|
||||
isUploadModalOpen = false;
|
||||
provFile: File;
|
||||
chartFile: File;
|
||||
|
||||
// For View swtich
|
||||
isCardView: boolean;
|
||||
cardHover = false;
|
||||
listHover = false;
|
||||
|
||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||
currentPage = 1;
|
||||
totalCount = 0;
|
||||
currentState: State;
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private translateService: TranslateService,
|
||||
private systemInfoService: SystemInfoService,
|
||||
private helmChartService: HelmChartService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
public get registryUrl(): string {
|
||||
return this.systemInfo ? this.systemInfo.registry_url : "";
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Get system info for tag views
|
||||
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
|
||||
.then(systemInfo => (this.systemInfo = systemInfo))
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
this.lastFilteredChartName = "";
|
||||
this.refresh();
|
||||
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.loading = true;
|
||||
this.helmChartService
|
||||
.getHelmCharts(this.projectName)
|
||||
.finally(() => {
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 3000);
|
||||
this.loading = false;
|
||||
})
|
||||
.subscribe(
|
||||
charts => {
|
||||
this.charts = charts;
|
||||
this.chartsCopy = charts.map(x => Object.assign({}, x));
|
||||
},
|
||||
err => {
|
||||
this.errorHandler.error(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onChartClick(item: HelmChartItem) {
|
||||
this.chartClickEvt.emit(item.name);
|
||||
}
|
||||
|
||||
onChartUpload() {
|
||||
this.isUploadModalOpen = true;
|
||||
}
|
||||
|
||||
upload() {
|
||||
if (!this.chartFile && !this.provFile) {
|
||||
return;
|
||||
}
|
||||
if (this.isUploading) { return; };
|
||||
this.isUploading = true;
|
||||
this.helmChartService
|
||||
.uploadChart(this.projectName, this.chartFile, this.provFile)
|
||||
.finally(() => {
|
||||
this.isUploading = false;
|
||||
this.isUploadModalOpen = false;
|
||||
this.refresh();
|
||||
})
|
||||
.subscribe(() => {
|
||||
this.translateService
|
||||
.get("HELM_CHART.FILE_UPLOADED")
|
||||
.subscribe(res => this.errorHandler.info(res));
|
||||
},
|
||||
err => this.errorHandler.error(err)
|
||||
);
|
||||
}
|
||||
|
||||
onChartFileChangeEvent(event) {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
this.chartFile = event.target.files[0];
|
||||
}
|
||||
}
|
||||
onProvFileChangeEvent(event) {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
this.provFile = event.target.files[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
showCard(cardView: boolean) {
|
||||
if (this.isCardView === cardView) {
|
||||
return;
|
||||
}
|
||||
this.isCardView = cardView;
|
||||
}
|
||||
|
||||
mouseEnter(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
this.cardHover = true;
|
||||
} else {
|
||||
this.listHover = true;
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeave(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
this.cardHover = false;
|
||||
} else {
|
||||
this.listHover = false;
|
||||
}
|
||||
}
|
||||
|
||||
isHovering(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
return this.cardHover;
|
||||
} else {
|
||||
return this.listHover;
|
||||
}
|
||||
}
|
||||
}
|
23
src/ui_ng/lib/src/helm-chart/index.ts
Normal file
23
src/ui_ng/lib/src/helm-chart/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Type } from '@angular/core';
|
||||
import { HelmChartComponent } from './helm-chart.component';
|
||||
import { ChartVersionComponent } from './versions/helm-chart-version.component';
|
||||
import { ChartDetailComponent } from './chart-detail/chart-detail.component';
|
||||
import { ChartDetailSummaryComponent } from './chart-detail/chart-detail-summary.component';
|
||||
import { ChartDetailDependencyComponent } from './chart-detail/chart-detail-dependency.component';
|
||||
import { ChartDetailValueComponent } from './chart-detail/chart-detail-value.component';
|
||||
|
||||
export * from "./helm-chart.component";
|
||||
export * from "./versions/helm-chart-version.component";
|
||||
export * from "./chart-detail/chart-detail.component";
|
||||
export * from "./chart-detail/chart-detail-summary.component";
|
||||
export * from "./chart-detail/chart-detail-dependency.component";
|
||||
export * from "./chart-detail/chart-detail-value.component";
|
||||
|
||||
export const HELMCHART_DIRECTIVE: Type<any>[] = [
|
||||
HelmChartComponent,
|
||||
ChartVersionComponent,
|
||||
ChartDetailComponent,
|
||||
ChartDetailSummaryComponent,
|
||||
ChartDetailDependencyComponent,
|
||||
ChartDetailValueComponent,
|
||||
];
|
@ -0,0 +1,136 @@
|
||||
<div>
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="col-xs-4">
|
||||
<div class="title-container">
|
||||
<div class="chart-name-span">
|
||||
{{chartName | translate}}
|
||||
</div>
|
||||
<div>
|
||||
{{roleName | translate}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row version-tool">
|
||||
<div class="toolbar">
|
||||
<div class="row flex-items-xs-right option-right rightPos">
|
||||
<div class="flex-xs-middle">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'HELM_CHART.FILTER_FOR_CHARTS' | translate}}" [currentValue]="lastFilteredVersionName"></hbr-filter>
|
||||
<span class="card-btn" (click)="showCard(true)" (mouseenter)="mouseEnter('card') " (mouseleave)="mouseLeave('card')">
|
||||
<clr-icon [ngClass]="{'is-highlight': isCardView || isHovering('card') }" shape="view-cards"></clr-icon>
|
||||
</span>
|
||||
<span class="list-btn" (click)="showCard(false)" (mouseenter)="mouseEnter('list') " (mouseleave)="mouseLeave('list')">
|
||||
<clr-icon [ngClass]="{'is-highlight': !isCardView || isHovering('list') }" shape="view-list"></clr-icon>
|
||||
</span>
|
||||
<span class="filter-divider"></span>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div *ngIf="!isCardView" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid (clrDgRefresh)="refresh($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRows">
|
||||
<clr-dg-action-bar>
|
||||
<button type="button" class="btn btn-sm btn-secondary"
|
||||
[disabled]="!hasProjectAdminRole"
|
||||
(click)="versionUpload($event)">
|
||||
<clr-icon shape="upload" size="16"></clr-icon> {{'HELM_CHART.UPLOAD' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary"
|
||||
[disabled]="!(selectedRows.length===1)"
|
||||
(click)="versionDownload()">
|
||||
<clr-icon shape="download" size="16"></clr-icon> {{'HELM_CHART.DOWNLOAD' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary"
|
||||
[disabled]="selectedRows.length<=0 || !hasProjectAdminRole"
|
||||
(click)="openVersionDeleteModal(selectedRows)">
|
||||
<clr-icon shape="times" size="16"></clr-icon> {{'BUTTON.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'HELM_CHART.VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'HELM_CHART.ENGINE' | 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-row *ngFor="let v of chartVersions" [clrDgItem]="v">
|
||||
<clr-dg-cell>
|
||||
<span class="list-img"><img [src]="getImgLink(v)"/></span>
|
||||
<a href="javascript:void(0)" (click)="onVersionClick(v)">{{ v.version }}</a>
|
||||
</clr-dg-cell>
|
||||
<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-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize">
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'HELM_CHART.OF' | translate}} {{pagination.totalItems}} {{'HELM_CHART.VERSIONS'
|
||||
| translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isCardView" class="row card-container">
|
||||
<div *ngFor="let item of chartVersions;" class="col-lg-3 col-md-4 col-sm-6">
|
||||
<a let i=index; class="card clickable" (click)="onVersionClick(item)">
|
||||
<div class="card-header">
|
||||
<div class="card-media-block">
|
||||
<img [src]="getImgLink(item)"/>
|
||||
<div class="card-media-description">
|
||||
<span class="card-media-title">{{item.name}}</span>
|
||||
<p class="card-media-text">{{item.home}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
<div class="form-group">
|
||||
<label>{{'HELM_CHART.ENGINE' | translate}}</label>
|
||||
<div>{{item.engine}}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{'HELM_CHART.MAINTAINERS' | translate}}</label>
|
||||
<div>{{getMaintainerString(item.maintainers)}}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{'HELM_CHART.VERSION' | translate}}</label>
|
||||
<div>{{item.appVersion}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false">
|
||||
<button type="button" class="btn btn-link" (click)="versionDownload($event, item)">{{'HELM_CHART.DOWNLOAD' | translate}}</button>
|
||||
<button type="button" class="btn btn-link" (click)="openVersionDeleteModal([item])">{{'BUTTON.DELETE' | translate}}</button>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div *ngIf="loading" [ngClass]="{'central-block-loading': isFirstPage, 'central-block-loading-more': !isFirstPage}">
|
||||
<span class="vertical-helper"></span>
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
<clr-modal [(clrModalOpen)]="isUploadModalOpen" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">{{'HELM_CHART.UPLOAD_TITLE' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<form #chartUploadForm="ngForm" enctype="multipart/form-data" (ngSubmit)="upload()">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="chart"> {{'HELM_CHART.CHART_FILE' | translate}} </label>
|
||||
<input type="file" id="chart" name="chart" ngModel (change)="onChartFileChangeEvent($event)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="prov"> {{'HELM_CHART.CHART_PROV' | translate}} </label>
|
||||
<input type="file" id="prov" name="prov" ngModel (change)="onProvFileChangeEvent($event)">
|
||||
</div>
|
||||
</section>
|
||||
<button type="submit" class="btn btn-secondary" [disabled]="isUploading">
|
||||
<span *ngIf="!isUploading">{{'HELM_CHART.UPLOAD' | translate}}</span>
|
||||
<span *ngIf="isUploading" class="spinner spinner-inline">
|
||||
Loading...
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
|
||||
</div>
|
@ -0,0 +1,96 @@
|
||||
.title-container {
|
||||
display: flex;
|
||||
.chart-name-span {
|
||||
border-right: 1px solid gray;
|
||||
font-size: 27px;
|
||||
font-weight: normal;
|
||||
padding-right: 9px;
|
||||
margin-right: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.version-tool {
|
||||
position: relative;
|
||||
.toolbar {
|
||||
overflow: hidden;
|
||||
.rightPos {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
.filter-divider {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 2px;
|
||||
background-color: #cccccc;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
position: relative;
|
||||
top: 9px;
|
||||
margin-right: 6px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-container {
|
||||
margin-top: 21px;
|
||||
.card-header {
|
||||
.card-media-block {
|
||||
img {
|
||||
height: 45px;
|
||||
width: 45px;
|
||||
}
|
||||
.card-media-description {
|
||||
height: 45px;
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.card-media-title {
|
||||
overflow: hidden;
|
||||
height: 24px;
|
||||
}
|
||||
.card-media-text {
|
||||
overflow: hidden;
|
||||
height: 21px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.card-block {
|
||||
margin-top: 24px;
|
||||
min-height: 100px;
|
||||
.form-group {
|
||||
display: flex;
|
||||
label {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
margin-top: 0px;
|
||||
}
|
||||
.card-footer {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.list-img {
|
||||
img {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-helper {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
vertical-align: middle;
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from "@angular/core";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import "rxjs/add/observable/forkJoin";
|
||||
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { State } from "clarity-angular";
|
||||
|
||||
import {
|
||||
SystemInfo,
|
||||
SystemInfoService,
|
||||
HelmChartVersion,
|
||||
HelmChartMaintainer
|
||||
} from "./../../service/index";
|
||||
import { ErrorHandler } from "./../../error-handler/error-handler";
|
||||
import { toPromise, DEFAULT_PAGE_SIZE, downloadFile } from "../../utils";
|
||||
import { OperationService } from "./../../operation/operation.service";
|
||||
import { HelmChartService } from "./../../service/helm-chart.service";
|
||||
import { ConfirmationAcknowledgement, ConfirmationDialogComponent, ConfirmationMessage } from "./../../confirmation-dialog";
|
||||
import {
|
||||
OperateInfo,
|
||||
OperationState,
|
||||
operateChanges
|
||||
} from "./../../operation/operate";
|
||||
import {
|
||||
ConfirmationButtons,
|
||||
ConfirmationTargets,
|
||||
ConfirmationState,
|
||||
DefaultHelmIcon
|
||||
} from "../../shared/shared.const";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-helm-chart-version",
|
||||
templateUrl: "./helm-chart-version.component.html",
|
||||
styleUrls: ["./helm-chart-version.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChartVersionComponent implements OnInit {
|
||||
signedCon: { [key: string]: any | string[] } = {};
|
||||
@Input() projectName: string;
|
||||
@Input() chartName: string;
|
||||
@Input() roleName: string;
|
||||
@Input() hasSignedIn: boolean;
|
||||
@Input() hasProjectAdminRole: boolean;
|
||||
@Output() versionClickEvt = new EventEmitter<string>();
|
||||
@Output() backEvt = new EventEmitter<any>();
|
||||
|
||||
lastFilteredVersionName: string;
|
||||
chartVersions: HelmChartVersion[] = [];
|
||||
versionsCopy: HelmChartVersion[] = [];
|
||||
systemInfo: SystemInfo;
|
||||
selectedRows: HelmChartVersion[] = [];
|
||||
loading = true;
|
||||
|
||||
isCardView: boolean;
|
||||
cardHover = false;
|
||||
listHover = false;
|
||||
|
||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||
currentPage = 1;
|
||||
totalCount = 0;
|
||||
currentState: State;
|
||||
|
||||
isUploading = false;
|
||||
isUploadModalOpen = false;
|
||||
chartFile: File;
|
||||
provFile: File;
|
||||
|
||||
@ViewChild("confirmationDialog")
|
||||
confirmationDialog: ConfirmationDialogComponent;
|
||||
|
||||
@ViewChild("chartUploadForm") form: NgForm;
|
||||
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private translateService: TranslateService,
|
||||
private systemInfoService: SystemInfoService,
|
||||
private helmChartService: HelmChartService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private operationService: OperationService,
|
||||
) {}
|
||||
|
||||
public get registryUrl(): string {
|
||||
return this.systemInfo ? this.systemInfo.registry_url : "";
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Get system info for tag views
|
||||
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
|
||||
.then(systemInfo => (this.systemInfo = systemInfo))
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
this.refresh();
|
||||
this.lastFilteredVersionName = "";
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.loading = true;
|
||||
this.helmChartService
|
||||
.getChartVersions(this.projectName, this.chartName)
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 2000);
|
||||
})
|
||||
.subscribe(
|
||||
versions => {
|
||||
this.chartVersions = versions;
|
||||
this.versionsCopy = versions.map(x => Object.assign({}, x));
|
||||
},
|
||||
err => {
|
||||
if (err.status && err.status === 404) {
|
||||
this.backEvt.emit();
|
||||
}
|
||||
this.errorHandler.error(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getMaintainerString(maintainers: HelmChartMaintainer[]) {
|
||||
if (!maintainers || maintainers.length < 1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let maintainer_string = maintainers[0].name;
|
||||
if (maintainers.length > 1) {
|
||||
maintainer_string = `${maintainer_string} (${maintainers.length - 1} others)`;
|
||||
}
|
||||
return maintainer_string;
|
||||
}
|
||||
|
||||
onVersionClick(version: HelmChartVersion) {
|
||||
this.versionClickEvt.emit(version.version);
|
||||
}
|
||||
|
||||
deleteVersion(version: HelmChartVersion): Observable<any> {
|
||||
// init operation info
|
||||
let operateMsg = new OperateInfo();
|
||||
operateMsg.name = "OPERATION.DELETE_CHART_VERSION";
|
||||
operateMsg.data.id = version.digest;
|
||||
operateMsg.state = OperationState.progressing;
|
||||
operateMsg.data.name = `${version.name}:${version.version}`;
|
||||
this.operationService.publishInfo(operateMsg);
|
||||
|
||||
return this.helmChartService
|
||||
.deleteChartVersion(this.projectName, this.chartName, version.version)
|
||||
.map(
|
||||
() => operateChanges(operateMsg, OperationState.success),
|
||||
err => operateChanges(operateMsg, OperationState.failure, err)
|
||||
);
|
||||
}
|
||||
|
||||
deleteVersions(versions: HelmChartVersion[]) {
|
||||
if (versions && versions.length < 1) { return; }
|
||||
let versionObs = versions.map(v => this.deleteVersion(v));
|
||||
Observable.forkJoin(versionObs).finally(() => this.refresh()).subscribe();
|
||||
}
|
||||
|
||||
versionDownload(item?: HelmChartVersion) {
|
||||
let selectedVersion: HelmChartVersion;
|
||||
|
||||
if (item) {
|
||||
selectedVersion = item;
|
||||
} else {
|
||||
// return if selected version less then 1
|
||||
if (this.selectedRows.length < 1) {
|
||||
return;
|
||||
}
|
||||
selectedVersion = this.selectedRows[0];
|
||||
}
|
||||
if (!selectedVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
let filename = selectedVersion.urls[0];
|
||||
this.helmChartService.downloadChart(this.projectName, filename).subscribe(
|
||||
res => {
|
||||
downloadFile(res);
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
versionUpload() {
|
||||
this.isUploadModalOpen = true;
|
||||
}
|
||||
|
||||
showCard(cardView: boolean) {
|
||||
if (this.isCardView === cardView) {
|
||||
return;
|
||||
}
|
||||
this.isCardView = cardView;
|
||||
}
|
||||
|
||||
mouseEnter(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
this.cardHover = true;
|
||||
} else {
|
||||
this.listHover = true;
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeave(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
this.cardHover = false;
|
||||
} else {
|
||||
this.listHover = false;
|
||||
}
|
||||
}
|
||||
|
||||
isHovering(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
return this.cardHover;
|
||||
} else {
|
||||
return this.listHover;
|
||||
}
|
||||
}
|
||||
|
||||
upload() {
|
||||
if (!this.chartFile && !this.provFile) {
|
||||
return;
|
||||
}
|
||||
if (this.isUploading) { return; };
|
||||
this.isUploading = true;
|
||||
this.helmChartService
|
||||
.uploadChart(this.projectName, this.chartFile, this.provFile)
|
||||
.finally(() => {
|
||||
this.isUploading = false;
|
||||
this.isUploadModalOpen = false;
|
||||
this.refresh();
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 3000);
|
||||
})
|
||||
.subscribe(
|
||||
() => {
|
||||
this.translateService.get("HELM_CHART.FILE_UPLOADED")
|
||||
.subscribe(res => this.errorHandler.info(res));
|
||||
},
|
||||
err => this.errorHandler.error(err)
|
||||
);
|
||||
}
|
||||
|
||||
onChartFileChangeEvent(event) {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
this.chartFile = event.target.files[0];
|
||||
}
|
||||
}
|
||||
onProvFileChangeEvent(event) {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
this.provFile = event.target.files[0];
|
||||
}
|
||||
}
|
||||
|
||||
openVersionDeleteModal(versions: HelmChartVersion[]) {
|
||||
let versionNames = versions.map(v => v.name).join(",");
|
||||
this.translateService.get("HELM_CHART.DELETE_CHART_VERSION").subscribe(key => {
|
||||
let message = new ConfirmationMessage(
|
||||
"HELM_CHART.DELETE_CHART_VERSION_TITLE",
|
||||
key,
|
||||
versionNames,
|
||||
versions,
|
||||
ConfirmationTargets.HELM_CHART,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.confirmationDialog.open(message);
|
||||
let hnd = setInterval(() => this.cdr.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 2000);
|
||||
});
|
||||
}
|
||||
|
||||
confirmDeletion(message: ConfirmationAcknowledgement) {
|
||||
if (
|
||||
message &&
|
||||
message.source === ConfirmationTargets.HELM_CHART &&
|
||||
message.state === ConfirmationState.CONFIRMED
|
||||
) {
|
||||
let versions = message.data;
|
||||
this.deleteVersions(versions);
|
||||
}
|
||||
}
|
||||
|
||||
getImgLink(v: HelmChartVersion) {
|
||||
if (v.icon) {
|
||||
return v.icon;
|
||||
} else {
|
||||
return DefaultHelmIcon;
|
||||
}
|
||||
}
|
||||
}
|
@ -26,3 +26,4 @@ export * from './gridview/index';
|
||||
export * from './repository-gridview/index';
|
||||
export * from './operation/index';
|
||||
export * from './_animations/index';
|
||||
export * from './helm-chart/index';
|
||||
|
@ -47,8 +47,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hbr-gridview *ngIf="isCardView" #gridView style="position:relative;" [items]="repositories" [loading]="loading" [pageSize]="pageSize"
|
||||
[currentPage]="currentPage" [totalCount]="totalCount" [expectScrollPercent]="90" [withAdmiral]="withAdmiral" (loadNextPageEvent)="loadNextPage()">
|
||||
<hbr-gridview *ngIf="isCardView" #gridView style="position:relative;"
|
||||
[items]="repositories"
|
||||
[loading]="loading"
|
||||
[pageSize]="pageSize"
|
||||
[currentPage]="currentPage"
|
||||
[totalCount]="totalCount"
|
||||
[expectScrollPercent]="90"
|
||||
[withAdmiral]="withAdmiral"
|
||||
(loadNextPageEvent)="loadNextPage()">
|
||||
<ng-template let-item="item">
|
||||
<a class="card clickable" (click)="watchRepoClickEvt(item)">
|
||||
<div class="card-header">
|
||||
@ -84,7 +91,7 @@
|
||||
<button *ngIf="withAdmiral" type="button" class="btn btn-link" clrDropdownItem (click)="itemAddInfoEvent($event, item)" [disabled]="!hasProjectAdminRole">
|
||||
{{'REPOSITORY.ADDITIONAL_INFO' | translate}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-link" clrDropdownItem (click)="deleteItemEvent($event, item)" [disabled]="!hasProjectAdminRole">
|
||||
<button type="button" class="btn btn-link" clrDropdownItem (click)="deleteItemEvent($event, [item])" [disabled]="!hasProjectAdminRole">
|
||||
{{'REPOSITORY.DELETE' | translate}}
|
||||
</button>
|
||||
</clr-dropdown-menu>
|
||||
|
@ -220,4 +220,22 @@ export interface IServiceConfig {
|
||||
* @memberOf IServiceConfig
|
||||
*/
|
||||
labelEndpoint?: string;
|
||||
|
||||
/**
|
||||
* The base endpoint of the service used to handle the helm chart.
|
||||
* helm charts related endpoints will be built based on this endpoint.
|
||||
* E.g:
|
||||
* If the base endpoint is '/api/helmcharts',
|
||||
* the helm chart endpoint will be '/api/helmcharts/:id'.
|
||||
*
|
||||
* @type {string}
|
||||
* @memberOf IServiceConfig
|
||||
*/
|
||||
helmChartEndpoint?: string;
|
||||
|
||||
/**
|
||||
* The base endpoint of the chart download url
|
||||
* @type {string}
|
||||
*/
|
||||
downloadChartEndpoint?: string;
|
||||
}
|
||||
|
236
src/ui_ng/lib/src/service/helm-chart.service.ts
Normal file
236
src/ui_ng/lib/src/service/helm-chart.service.ts
Normal file
@ -0,0 +1,236 @@
|
||||
import { Injectable, Inject } from "@angular/core";
|
||||
import { Http, Response, ResponseContentType } from "@angular/http";
|
||||
|
||||
import "rxjs/add/observable/of";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
|
||||
import { RequestQueryParams } from "./RequestQueryParams";
|
||||
import { HelmChartItem, HelmChartVersion, HelmChartDetail } from "./interface";
|
||||
import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
|
||||
import { HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from "../utils";
|
||||
|
||||
|
||||
/**
|
||||
* Define service methods for handling the helmchart related things.
|
||||
* Loose couple with project module.
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class RepositoryService
|
||||
*/
|
||||
export abstract class HelmChartService {
|
||||
/**
|
||||
* Get all helm charts info
|
||||
* @param projectName Id of the project
|
||||
* @param queryParams options params for query data
|
||||
*/
|
||||
abstract getHelmCharts(
|
||||
projectName: string,
|
||||
queryParams?: RequestQueryParams
|
||||
): Observable<HelmChartItem[]>;
|
||||
|
||||
/**
|
||||
* Delete an helmchart
|
||||
* @param projectId Id of the project
|
||||
* @param chartId ID of helmChart in this specific project
|
||||
*/
|
||||
abstract deleteHelmChart(projectId: number | string, chartId: number): Observable<any>;
|
||||
|
||||
/**
|
||||
* Get all the versions of helmchart
|
||||
* @param projectName Id of the project
|
||||
* @param chartName ID of the helm chart
|
||||
* @param queryParams option params for query
|
||||
*/
|
||||
abstract getChartVersions(
|
||||
projectName: string,
|
||||
chartName: string,
|
||||
): Observable<HelmChartVersion[]>;
|
||||
|
||||
/**
|
||||
* Delete a version of helmchart
|
||||
* @param projectName ID of the project
|
||||
* @param chartName ID of the chart you want to delete
|
||||
* @param version name of the version
|
||||
*/
|
||||
abstract deleteChartVersion(projectName: string, chartName: string, version: string): Observable<any>;
|
||||
|
||||
/**
|
||||
* Get the all details of an helmchart
|
||||
* @param projectName ID of the project
|
||||
* @param chartname ID of the chart
|
||||
* @param version name of the chart's version
|
||||
* @param queryParams options
|
||||
*/
|
||||
abstract getChartDetail(
|
||||
projectName: string,
|
||||
chartname: string,
|
||||
version: string,
|
||||
): Observable<HelmChartDetail>;
|
||||
|
||||
/**
|
||||
* Download an specific verison
|
||||
* @param projectName ID of the project
|
||||
* @param filename ID of the helm chart
|
||||
* @param version Name of version
|
||||
* @param queryParams options
|
||||
*/
|
||||
abstract downloadChart(
|
||||
projectName: string,
|
||||
filename: string,
|
||||
): Observable<any>;
|
||||
|
||||
/**
|
||||
* Upload chart and prov files to chartmuseam
|
||||
* @param projectName Name of the project
|
||||
* @param chart chart file
|
||||
* @param prov prov file
|
||||
*/
|
||||
abstract uploadChart (
|
||||
projectName: string,
|
||||
chart: File,
|
||||
prov: File
|
||||
): Observable<any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement default service for helm chart.
|
||||
*/
|
||||
@Injectable()
|
||||
export class HelmChartDefaultService extends HelmChartService {
|
||||
constructor(
|
||||
private http: Http,
|
||||
@Inject(SERVICE_CONFIG) private config: IServiceConfig
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private extractData(res: Response) {
|
||||
if (res.text() === "") {
|
||||
return [];
|
||||
}
|
||||
return res.json() || [];
|
||||
}
|
||||
|
||||
private extractHelmItems(res: Response) {
|
||||
if (res.text() === "") {
|
||||
return [];
|
||||
}
|
||||
let charts = res.json();
|
||||
if (charts) {
|
||||
return charts.map( chart => {
|
||||
return {
|
||||
name: chart.Name,
|
||||
total_versions: chart.total_versions,
|
||||
created: chart.Created,
|
||||
icon: chart.Icon,
|
||||
home: chart.Home};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private handleErrorObservable(error: Response | any) {
|
||||
console.error(error.message || error);
|
||||
return Observable.throw(error.message || error);
|
||||
}
|
||||
|
||||
public getHelmCharts(
|
||||
projectName: string,
|
||||
): Observable<HelmChartItem[]> {
|
||||
if (!projectName) {
|
||||
return Observable.throw("Bad argument, No project id to get helm charts");
|
||||
}
|
||||
|
||||
return this.http
|
||||
.get(`${this.config.helmChartEndpoint}/${projectName}/charts`, HTTP_GET_OPTIONS)
|
||||
.map(response => {
|
||||
return this.extractHelmItems(response);
|
||||
})
|
||||
.catch(error => {
|
||||
return this.handleErrorObservable(error);
|
||||
});
|
||||
}
|
||||
|
||||
public deleteHelmChart(projectId: number | string, chartId: number): any {
|
||||
if (!chartId) {
|
||||
Observable.throw("Bad argument");
|
||||
}
|
||||
|
||||
return this.http
|
||||
.delete(`${this.config.helmChartEndpoint}/${projectId}/${chartId}`)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
public getChartVersions(
|
||||
projectName: string,
|
||||
chartName: string,
|
||||
): Observable<HelmChartVersion[]> {
|
||||
return this.http.get(`${this.config.helmChartEndpoint}/${projectName}/charts/${chartName}`, HTTP_GET_OPTIONS)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
public deleteChartVersion(projectName: string, chartName: string, version: string): any {
|
||||
return this.http.delete(`${this.config.helmChartEndpoint}/${projectName}/charts/${chartName}/${version}`, HTTP_JSON_OPTIONS)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
public getChartDetail (
|
||||
projectName: string,
|
||||
chartName: string,
|
||||
version: string,
|
||||
): Observable<HelmChartDetail> {
|
||||
return this.http.get(`${this.config.helmChartEndpoint}/${projectName}/charts/${chartName}/${version}`)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
public downloadChart(
|
||||
projectName: string,
|
||||
filename: string,
|
||||
): Observable<any> {
|
||||
return this.http.get(`${this.config.downloadChartEndpoint}/${projectName}/${filename}`, {
|
||||
responseType: ResponseContentType.Blob,
|
||||
})
|
||||
.map(response => {
|
||||
return {
|
||||
filename: filename.split('/')[1],
|
||||
data: response.blob()
|
||||
};
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
public uploadChart(
|
||||
projectName: string,
|
||||
chart?: File,
|
||||
prov?: File
|
||||
): Observable<any> {
|
||||
let formData = new FormData();
|
||||
let uploadURL = `${this.config.helmChartEndpoint}/${projectName}/charts`;
|
||||
if (chart) {
|
||||
formData.append('chart', chart);
|
||||
}
|
||||
if (prov) {
|
||||
formData.append('prov', prov);
|
||||
if (!chart) {
|
||||
uploadURL = `${this.config.helmChartEndpoint}/${projectName}/prov`;
|
||||
}
|
||||
}
|
||||
return this.http.post(uploadURL, formData)
|
||||
.map(reponse => this.extractData(reponse))
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
}
|
@ -11,3 +11,4 @@ export * from './configuration.service';
|
||||
export * from './job-log.service';
|
||||
export * from './project.service';
|
||||
export * from './label.service';
|
||||
export * from './helm-chart.service';
|
||||
|
@ -296,3 +296,80 @@ export interface ScrollPosition {
|
||||
sT: number;
|
||||
cH: number;
|
||||
}
|
||||
|
||||
export interface HelmChartItem {
|
||||
name: string;
|
||||
total_versions: number;
|
||||
created: string;
|
||||
icon: string;
|
||||
home: string;
|
||||
status?: string;
|
||||
pulls?: number;
|
||||
maintainer?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface HelmChartVersion {
|
||||
name: string;
|
||||
home: string;
|
||||
sources: string[];
|
||||
version: string;
|
||||
description: string;
|
||||
keywords: string[];
|
||||
maintainers: HelmChartMaintainer[];
|
||||
engine: string;
|
||||
icon: string;
|
||||
appVersion: string;
|
||||
urls: string[];
|
||||
created: string;
|
||||
digest: string;
|
||||
}
|
||||
|
||||
export interface HelmChartDetail {
|
||||
metadata: HelmChartMetaData;
|
||||
dependencies: HelmChartDependency[];
|
||||
values: any;
|
||||
files: HelmchartFile;
|
||||
security: HelmChartSecurity;
|
||||
}
|
||||
|
||||
export interface HelmChartMetaData {
|
||||
name: string;
|
||||
home: string;
|
||||
sources: string[];
|
||||
version: string;
|
||||
description: string;
|
||||
keywords: string[];
|
||||
maintainers: HelmChartMaintainer[];
|
||||
engine: string;
|
||||
icon: string;
|
||||
appVersion: string;
|
||||
urls: string[];
|
||||
created?: string;
|
||||
digest: string;
|
||||
}
|
||||
|
||||
export interface HelmChartMaintainer {
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface HelmChartDependency {
|
||||
name: string;
|
||||
version: string;
|
||||
repository: string;
|
||||
}
|
||||
|
||||
export interface HelmchartFile {
|
||||
"README.MD": string;
|
||||
"values.yaml": string;
|
||||
}
|
||||
|
||||
export interface HelmChartSecurity {
|
||||
signature: HelmChartSignature;
|
||||
}
|
||||
|
||||
export interface HelmChartSignature {
|
||||
signed: boolean;
|
||||
prov_file: string;
|
||||
}
|
||||
|
@ -40,7 +40,8 @@ export const enum ConfirmationTargets {
|
||||
TAG,
|
||||
CONFIG,
|
||||
CONFIG_ROUTE,
|
||||
CONFIG_TAB
|
||||
CONFIG_TAB,
|
||||
HELM_CHART
|
||||
};
|
||||
|
||||
export const enum ActionType {
|
||||
@ -87,3 +88,7 @@ export const LabelColor = [
|
||||
{ 'color': '#F52F52', 'textColor': 'black' }, { 'color': '#FF5501', 'textColor': 'black' },
|
||||
{ 'color': '#F57600', 'textColor': 'black' }, { 'color': '#FFDC0B', 'textColor': 'black' },
|
||||
];
|
||||
|
||||
export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' };
|
||||
|
||||
export const DefaultHelmIcon = '/static/images/helm-logo.svg';
|
||||
|
@ -5,6 +5,7 @@ import { ClarityModule } from 'clarity-angular';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { TranslateModule, TranslateLoader, MissingTranslationHandler } from '@ngx-translate/core';
|
||||
import { CookieService, CookieModule } from 'ngx-cookie';
|
||||
import { MarkdownModule } from 'ngx-markdown';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
|
||||
import { ClipboardModule } from '../third-party/ngx-clipboard/index';
|
||||
@ -46,6 +47,7 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) {
|
||||
ClipboardModule,
|
||||
CookieModule.forRoot(),
|
||||
ClarityModule.forRoot(),
|
||||
MarkdownModule.forRoot(),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
@ -63,9 +65,10 @@ export function GeneralTranslatorLoader(http: Http, config: IServiceConfig) {
|
||||
HttpModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CookieModule,
|
||||
ClipboardModule,
|
||||
ClarityModule,
|
||||
CookieModule,
|
||||
MarkdownModule,
|
||||
TranslateModule,
|
||||
],
|
||||
providers: [CookieService]
|
||||
|
@ -45,27 +45,3 @@ export const errorHandler = function (error: any): string {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class CancelablePromise<T> {
|
||||
|
||||
constructor(promise: Promise<T>) {
|
||||
this.wrappedPromise = new Promise((resolve, reject) => {
|
||||
promise.then((val) =>
|
||||
this.isCanceled ? reject({isCanceled: true}) : resolve(val)
|
||||
);
|
||||
promise.catch((error) =>
|
||||
this.isCanceled ? reject({isCanceled: true}) : reject(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private wrappedPromise: Promise<T>;
|
||||
private isCanceled: boolean;
|
||||
getPromise(): Promise<T> {
|
||||
return this.wrappedPromise;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.isCanceled = true;
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,12 @@ export const HTTP_GET_OPTIONS: RequestOptions = new RequestOptions({
|
||||
})
|
||||
});
|
||||
|
||||
export const FILE_UPLOAD_OPTION: RequestOptions = new RequestOptions({
|
||||
headers: new Headers({
|
||||
"Content-Type": 'multipart/form-data',
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* Build http request options
|
||||
*
|
||||
@ -288,3 +294,15 @@ export function clone(srcObj: any): any {
|
||||
if (!srcObj) { return null; };
|
||||
return JSON.parse(JSON.stringify(srcObj));
|
||||
}
|
||||
|
||||
export function downloadFile(fileData) {
|
||||
let url = window.URL.createObjectURL(fileData.data);
|
||||
let a = document.createElement("a");
|
||||
document.body.appendChild(a);
|
||||
a.setAttribute("style", "display: none");
|
||||
a.href = url;
|
||||
a.download = fileData.filename;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
};
|
||||
|
13292
src/ui_ng/package-lock.json
generated
Normal file
13292
src/ui_ng/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"start": "ng serve --ssl 1 --ssl-key ssl/server.key --ssl-cert ssl/server.crt --host 0.0.0.0 --proxy-config proxy.config.json",
|
||||
"lint": "tslint \"src/**/*.ts\"",
|
||||
"lint:lib": "tslint \"lib/**/*.ts\"",
|
||||
"lint:lib": "tslint \"lib/**/*.ts\" -e \"lib/dist/**/*\" ",
|
||||
"test": "ng test --single-run",
|
||||
"pree2e": "webdriver-manager update",
|
||||
"e2e": "protractor",
|
||||
@ -35,6 +35,7 @@
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
"ngx-markdown": "1.5.2",
|
||||
"rxjs": "^5.0.1",
|
||||
"ts-helpers": "^1.1.1",
|
||||
"web-animations-js": "^2.2.1",
|
||||
|
@ -21,7 +21,11 @@ export default {
|
||||
plugins: [
|
||||
nodeResolve({jsnext: true, module: true, browser: true}),
|
||||
commonjs({
|
||||
include: ['node_modules/**'],
|
||||
namedExports: {
|
||||
'node_modules/ngx-markdown/dist/lib/index.js': ['MarkdownModule']
|
||||
},
|
||||
include: ['node_modules/**',
|
||||
'node_modules/ngx-markdown/**'],
|
||||
}),
|
||||
uglify()
|
||||
]
|
||||
|
@ -18,6 +18,7 @@ import { HttpModule } from '@angular/http';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ClarityModule } from 'clarity-angular';
|
||||
import { CookieModule } from 'ngx-cookie';
|
||||
import { MarkdownModule } from 'ngx-markdown';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -26,6 +27,7 @@ import { CookieModule } from 'ngx-cookie';
|
||||
HttpModule,
|
||||
ClarityModule.forRoot(),
|
||||
CookieModule.forRoot(),
|
||||
MarkdownModule.forRoot(),
|
||||
BrowserAnimationsModule
|
||||
],
|
||||
exports: [
|
||||
@ -33,7 +35,8 @@ import { CookieModule } from 'ngx-cookie';
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
ClarityModule,
|
||||
BrowserAnimationsModule
|
||||
BrowserAnimationsModule,
|
||||
MarkdownModule
|
||||
]
|
||||
})
|
||||
export class CoreModule {
|
||||
|
@ -47,6 +47,9 @@ import { MemberComponent } from './project/member/member.component';
|
||||
import {ProjectLabelComponent} from "./project/project-label/project-label.component";
|
||||
import { ProjectConfigComponent } from './project/project-config/project-config.component';
|
||||
import { ProjectRoutingResolver } from './project/project-routing-resolver.service';
|
||||
import { ListChartsComponent } from './project/list-charts/list-charts.component';
|
||||
import { ListChartVersionsComponent } from './project/list-chart-versions/list-chart-versions.component';
|
||||
import { ChartDetailComponent } from './project/chart-detail/chart-detail.component';
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
@ -116,6 +119,22 @@ const harborRoutes: Routes = [
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'projects/:id/helm-charts/:chart/versions',
|
||||
component: ListChartVersionsComponent,
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'projects/:id/helm-charts/:chart/versions/:version',
|
||||
component: ChartDetailComponent,
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'projects/:id',
|
||||
component: ProjectDetailComponent,
|
||||
@ -128,6 +147,10 @@ const harborRoutes: Routes = [
|
||||
path: 'repositories',
|
||||
component: RepositoryPageComponent
|
||||
},
|
||||
{
|
||||
path: 'helm-charts',
|
||||
component: ListChartsComponent
|
||||
},
|
||||
{
|
||||
path: 'repositories/:repo/tags',
|
||||
component: TagRepositoryComponent,
|
||||
|
@ -0,0 +1,16 @@
|
||||
<div>
|
||||
<div class="breadcrumb">
|
||||
<a (click)="gotoProjectList()"> {{ 'SIDE_NAV.PROJECTS'| translate}} </a>
|
||||
<
|
||||
<a (click)="gotoChartList()">{{ 'HELM_CHART.HELMCHARTS'| translate}}</a>
|
||||
<
|
||||
<a (click)="gotoChartVersion()">{{ 'HELM_CHART.CHARTVERSIONS'| translate}}</a>
|
||||
</div>
|
||||
<hbr-chart-detail
|
||||
[projectId]="projectId"
|
||||
[project]="project"
|
||||
[chartName]="chartName"
|
||||
[chartVersion]="chartVersion"
|
||||
[roleName]="roleName"
|
||||
></hbr-chart-detail>
|
||||
</div>
|
@ -0,0 +1,6 @@
|
||||
.breadcrumb a {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
color: #007cbb;
|
||||
font-size: 12px;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChartDetailComponent } from './chart-detail.component';
|
||||
|
||||
describe('ChartDetailComponent', () => {
|
||||
let component: ChartDetailComponent;
|
||||
let fixture: ComponentFixture<ChartDetailComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChartDetailComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChartDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
import { RoleMapping } from './../../shared/shared.const';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Project } from '../project';
|
||||
import { SessionService } from './../../shared/session.service';
|
||||
import { SessionUser } from './../../shared/session-user';
|
||||
|
||||
@Component({
|
||||
selector: "project-chart-detail",
|
||||
templateUrl: "./chart-detail.component.html",
|
||||
styleUrls: ["./chart-detail.component.scss"]
|
||||
})
|
||||
export class ChartDetailComponent implements OnInit {
|
||||
|
||||
projectId: number | string;
|
||||
project: Project;
|
||||
chartName: string;
|
||||
chartVersion: string;
|
||||
currentUser: SessionUser;
|
||||
hasProjectAdminRole: boolean;
|
||||
roleName: string;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private session: SessionService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.params['id'];
|
||||
this.chartName = this.route.snapshot.params['chart'];
|
||||
this.chartVersion = this.route.snapshot.params['version'];
|
||||
// Get current user from registered resolver.
|
||||
this.currentUser = this.session.getCurrentUser();
|
||||
let resolverData = this.route.snapshot.data;
|
||||
if (resolverData) {
|
||||
this.project = <Project>(resolverData["projectResolver"]);
|
||||
this.roleName = RoleMapping[this.project.role_name];
|
||||
this.hasProjectAdminRole = this.project.has_project_admin_role;
|
||||
}
|
||||
}
|
||||
|
||||
gotoProjectList() {
|
||||
this.router.navigateByUrl("/harbor/projects");
|
||||
}
|
||||
|
||||
gotoChartList() {
|
||||
this.router.navigateByUrl(`/harbor/projects/${this.projectId}/helm-charts`);
|
||||
}
|
||||
|
||||
gotoChartVersion() {
|
||||
this.router.navigateByUrl(`/harbor/projects/${this.projectId}/helm-charts/${this.chartName}/versions`);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<div>
|
||||
<div class="breadcrumb">
|
||||
<a href="javascript:void(0)" (click)="gotoProjectList()"> {{ 'SIDE_NAV.PROJECTS'| translate}} </a>
|
||||
<
|
||||
<a href="javascript:void(0)" (click)="gotoChartList()">{{ 'HELM_CHART.HELMCHARTS'| translate}}</a>
|
||||
</div>
|
||||
<hbr-helm-chart-version
|
||||
[projectName]='projectName'
|
||||
[chartName]='chartName'
|
||||
[roleName]='roleName'
|
||||
[hasSignedIn]='hasSignedIn'
|
||||
[hasProjectAdminRole]='hasProjectAdminRole'
|
||||
(versionClickEvt)='onVersionClick($event)'
|
||||
(backEvt)='gotoChartList()'>
|
||||
</hbr-helm-chart-version>
|
||||
</div>
|
@ -0,0 +1,6 @@
|
||||
.breadcrumb a {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
color: #007cbb;
|
||||
font-size: 12px;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListChartVersionsComponent } from './list-chart-versions.component';
|
||||
|
||||
describe('ListChartVersionsComponent', () => {
|
||||
let component: ListChartVersionsComponent;
|
||||
let fixture: ComponentFixture<ListChartVersionsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ListChartVersionsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListChartVersionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,60 @@
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Project } from './../project';
|
||||
import { SessionUser } from './../../shared/session-user';
|
||||
import { SessionService } from './../../shared/session.service';
|
||||
import { RoleMapping } from '../../shared/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'list-chart-version',
|
||||
templateUrl: './list-chart-versions.component.html',
|
||||
styleUrls: ['./list-chart-versions.component.scss']
|
||||
})
|
||||
export class ListChartVersionsComponent implements OnInit {
|
||||
|
||||
loading = false;
|
||||
|
||||
projectId: number;
|
||||
projectName: string;
|
||||
chartName: string;
|
||||
roleName: string;
|
||||
|
||||
hasSignedIn: boolean;
|
||||
hasProjectAdminRole: boolean;
|
||||
currentUser: SessionUser;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private session: SessionService) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.params['id'];
|
||||
this.chartName = this.route.snapshot.params['chart'];
|
||||
// Get current user from registered resolver.
|
||||
this.currentUser = this.session.getCurrentUser();
|
||||
let resolverData = this.route.snapshot.data;
|
||||
if (resolverData) {
|
||||
let project = <Project>(resolverData["projectResolver"]);
|
||||
this.hasProjectAdminRole = project.has_project_admin_role;
|
||||
this.roleName = RoleMapping[project.role_name];
|
||||
this.projectName = project.name;
|
||||
}
|
||||
}
|
||||
|
||||
onVersionClick(version: string) {
|
||||
this.router.navigateByUrl(`${this.router.url}/${version}`);
|
||||
}
|
||||
|
||||
gotoProjectList() {
|
||||
this.router.navigateByUrl('/harbor/projects');
|
||||
}
|
||||
|
||||
gotoChartList() {
|
||||
this.router.navigateByUrl(`/harbor/projects/${this.projectId}/helm-charts`);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<hbr-helm-chart
|
||||
[projectId]='projectId'
|
||||
[projectName]='projectName'
|
||||
[urlPrefix]='urlPrefix'
|
||||
[hasSignedIn]='hasSignedIn'
|
||||
[hasProjectAdminRole]='hasProjectAdminRole'
|
||||
(chartClickEvt)='onChartClick($event)'>
|
||||
</hbr-helm-chart>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListChartsComponent } from './list-charts.component';
|
||||
|
||||
describe('ListChartsComponent', () => {
|
||||
let component: ListChartsComponent;
|
||||
let fixture: ComponentFixture<ListChartsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ListChartsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListChartsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,44 @@
|
||||
import { Project } from '../../project/project';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { SessionService } from './../../shared/session.service';
|
||||
import { SessionUser } from './../../shared/session-user';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: "project-list-charts",
|
||||
templateUrl: "./list-charts.component.html",
|
||||
styleUrls: ["./list-charts.component.scss"]
|
||||
})
|
||||
export class ListChartsComponent implements OnInit {
|
||||
projectId: number;
|
||||
|
||||
projectName: string;
|
||||
urlPrefix: string;
|
||||
hasSignedIn: boolean;
|
||||
hasProjectAdminRole: boolean;
|
||||
currentUser: SessionUser;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private session: SessionService) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.parent.params["id"];
|
||||
// Get current user from registered resolver.
|
||||
this.currentUser = this.session.getCurrentUser();
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
if (resolverData) {
|
||||
let project = <Project>(resolverData["projectResolver"]);
|
||||
this.projectName = project.name;
|
||||
this.hasProjectAdminRole = project.has_project_admin_role;
|
||||
}
|
||||
}
|
||||
|
||||
onChartClick(chartName: string) {
|
||||
this.router.navigateByUrl(`${this.router.url}/${chartName}/versions`);
|
||||
}
|
||||
}
|
@ -1,22 +1,28 @@
|
||||
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="addNewProject()" *ngIf="projectCreationRestriction"><clr-icon shape="plus" size="16"></clr-icon> {{'PROJECT.NEW_PROJECT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length && (isSystemAdmin || canDelete))" (click)="deleteProjects(selectedRow)" ><clr-icon shape="times" size="16"></clr-icon> {{'PROJECT.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
|
||||
<clr-dg-action-bar>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="addNewProject()" *ngIf="projectCreationRestriction">
|
||||
<clr-icon shape="plus" size="16"></clr-icon> {{'PROJECT.NEW_PROJECT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length && (isSystemAdmin || canDelete))"
|
||||
(click)="deleteProjects(selectedRow)">
|
||||
<clr-icon shape="times" size="16"></clr-icon> {{'PROJECT.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'PROJECT.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="accessLevelComparator">{{'PROJECT.ACCESS_LEVEL' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="showRoleInfo" [clrDgSortBy]="roleComparator">{{'PROJECT.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="repoCountComparator">{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="timeComparator">{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
|
||||
<clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell>
|
||||
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
|
||||
<clr-dg-cell>
|
||||
<a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{ (p.metadata.public === 'true' ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="showRoleInfo">{{roleInfo[p.current_user_role_id] | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span> {{pagination.totalItems }} {{'PROJECT.ITEMS' | translate}}
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span> {{pagination.totalItems
|
||||
}} {{'PROJECT.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="currentPage" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
@ -7,6 +7,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="repositories" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="helm-charts" routerLinkActive="active">{{'PROJECT_DETAIL.HELMCHART' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSystemAdmin || isMember">
|
||||
<a class="nav-link" routerLink="members" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a>
|
||||
</li>
|
||||
|
@ -34,6 +34,9 @@ import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||
|
||||
import { TargetExistsValidatorDirective } from '../shared/target-exists-directive';
|
||||
import { ProjectLabelComponent } from "../project/project-label/project-label.component";
|
||||
import { ListChartsComponent } from './list-charts/list-charts.component';
|
||||
import { ListChartVersionsComponent } from './list-chart-versions/list-chart-versions.component';
|
||||
import { ChartDetailComponent } from './chart-detail/chart-detail.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -52,7 +55,10 @@ import { ProjectLabelComponent } from "../project/project-label/project-label.co
|
||||
AddMemberComponent,
|
||||
TargetExistsValidatorDirective,
|
||||
ProjectLabelComponent,
|
||||
AddGroupComponent
|
||||
AddGroupComponent,
|
||||
ListChartsComponent,
|
||||
ListChartVersionsComponent,
|
||||
ChartDetailComponent
|
||||
],
|
||||
exports: [ProjectComponent, ListProjectComponent],
|
||||
providers: [ProjectRoutingResolver, ProjectService, MemberService]
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<div class="arrow-block" *ngIf="!withAdmiral">
|
||||
<a (click)="goProBack()">< {{'SIDE_NAV.PROJECTS'| translate}}</a>
|
||||
<a (click)="watchGoBackEvt(projectId)">< {{'REPOSITORY.REPOSITORIES'| translate}}</a>
|
||||
<div class="breadcrumb" *ngIf="!withAdmiral">
|
||||
<a (click)="goProBack()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
|
||||
<a (click)="watchGoBackEvt(projectId)">< {{'REPOSITORY.REPOSITORIES'| translate}}</a>
|
||||
</div>
|
||||
<hbr-repository [repoName]="repoName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isGuest]="isGuest"
|
||||
(tagClickEvent)="watchTagClickEvt($event)" (backEvt)="watchGoBackEvt($event)"></hbr-repository>
|
||||
|
@ -1,4 +1,6 @@
|
||||
.sub-header-title {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.arrow-block a{text-decoration: none; cursor: pointer; cursor: pointer; color: #007cbb; font-size: 12px;}
|
||||
.breadcrumb a {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
color: #007cbb;
|
||||
font-size: 12px;
|
||||
}
|
@ -58,7 +58,23 @@ const uiLibConfig: IServiceConfig = {
|
||||
langCookieKey: "harbor-lang",
|
||||
langMessageLoader: "http",
|
||||
langMessagePathForHttpLoader: "i18n/lang/",
|
||||
langMessageFileSuffixForHttpLoader: "-lang.json"
|
||||
langMessageFileSuffixForHttpLoader: "-lang.json",
|
||||
systemInfoEndpoint: "/api/systeminfo",
|
||||
repositoryBaseEndpoint: "/api/repositories",
|
||||
logBaseEndpoint: "/api/logs",
|
||||
targetBaseEndpoint: "/api/targets",
|
||||
replicationBaseEndpoint: "/api/replications",
|
||||
replicationRuleEndpoint: "/api/policies/replication",
|
||||
replicationJobEndpoint: "/api/jobs/replication",
|
||||
vulnerabilityScanningBaseEndpoint: "/api/repositories",
|
||||
projectPolicyEndpoint: "/api/projects/configs",
|
||||
projectBaseEndpoint: "/api/projects",
|
||||
localI18nMessageVariableMap: {},
|
||||
configurationEndpoint: "/api/configurations",
|
||||
scanJobEndpoint: "/api/jobs/scan",
|
||||
labelEndpoint: "/api/labels",
|
||||
helmChartEndpoint: "/api/chartrepo",
|
||||
downloadChartEndpoint: "/chartrepo"
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
|
@ -185,7 +185,8 @@
|
||||
"LOGS": "Logs",
|
||||
"LABELS": "Labels",
|
||||
"PROJECTS": "Projects",
|
||||
"CONFIG": "Configuration"
|
||||
"CONFIG": "Configuration",
|
||||
"HELMCHART": "Helm Charts"
|
||||
},
|
||||
"PROJECT_CONFIG": {
|
||||
"REGISTRY": "Project registry",
|
||||
@ -311,7 +312,6 @@
|
||||
"TEST_CONNECTION_SUCCESS": "Connection tested successfully.",
|
||||
"TEST_CONNECTION_FAILURE": "Failed to ping endpoint.",
|
||||
"NAME": "Name",
|
||||
"STATUS": "Status",
|
||||
"PROJECT": "Project",
|
||||
"NAME_IS_REQUIRED": "Name is required.",
|
||||
"DESCRIPTION": "Description",
|
||||
@ -470,6 +470,50 @@
|
||||
"DEPLOY": "DEPLOY",
|
||||
"ADDITIONAL_INFO": "Add Additional Info"
|
||||
},
|
||||
"HELM_CHART": {
|
||||
"HELMCHARTS": "Charts",
|
||||
"CHARTVERSIONS": "Versions",
|
||||
"UPLOAD_TITLE": "Upload chart files",
|
||||
"CHART_FILE": "Chart File",
|
||||
"CHART_PROV": "Prov File",
|
||||
"DOWNLOAD": "Download",
|
||||
"SUMMARY": "Summary",
|
||||
"DEPENDENCIES": "Dependencies",
|
||||
"VALUES": "Values",
|
||||
"OVERVIEW": "Overview",
|
||||
"HOME": "Home",
|
||||
"SRC_REPO": "Srouce Repository",
|
||||
"CREATED": "Created Time",
|
||||
"MAINTAINERS": "Maintainers",
|
||||
"PULLS": "Pull Count",
|
||||
"VERSION": "Version",
|
||||
"INSTALL": "Install",
|
||||
"INSTALL_CHART": "Install Chart",
|
||||
"NAME": "Name",
|
||||
"REPO": "Repository",
|
||||
"FILTER_FOR_CHARTS": "Filter for charts",
|
||||
"DELETE": "Delete",
|
||||
"OF": "of",
|
||||
"VERSIONS": "versions",
|
||||
"IMAGES": "Images",
|
||||
"ENGINE": "Engine",
|
||||
"ACTION": "Action",
|
||||
"UPLOAD": "Upload",
|
||||
"DELETE_CHART_VERSION_TITLE": "Delete Chart Versions",
|
||||
"DELETE_CHART_VERSION": "Do you want to delete version {{param}}?",
|
||||
"IMPORT": "Import",
|
||||
"EXPORT": "Export",
|
||||
"ADD_REPO": "Add Repo",
|
||||
"SHOW_KV": "Key Value Pairs",
|
||||
"SHOW_YAML": "YAML File",
|
||||
"PLACEHOLDER": "No Item",
|
||||
"FILE_UPLOADED": "File upload successfully",
|
||||
"SIGNED": "Signed",
|
||||
"UNSIGNED": "Unsigned",
|
||||
"ITEMS": "charts",
|
||||
"NO_README": "No readme file provided by this charts.",
|
||||
"SECURITY": "Security"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet. Do you want to cancel?"
|
||||
},
|
||||
@ -725,6 +769,8 @@
|
||||
"DELETE_REPLICATION": "Delete replication",
|
||||
"DELETE_MEMBER": "Delete user member",
|
||||
"DELETE_GROUP": "Delete group member",
|
||||
"DELETE_CHART_VERSION": "Delete Chart Version",
|
||||
"DELETE_CHART": "Delete Chart",
|
||||
"SWITCH_ROLE": "Switch role",
|
||||
"ADD_GROUP": "Add group member",
|
||||
"ADD_USER": "Add user member",
|
||||
|
@ -311,7 +311,6 @@
|
||||
"TEST_CONNECTION_SUCCESS": "Conexión comprobada satisfactoriamente.",
|
||||
"TEST_CONNECTION_FAILURE": "Fallo al conectar con el endpoint.",
|
||||
"NAME": "Nombre",
|
||||
"STATUS": "Status",
|
||||
"PROJECT": "Proyecto",
|
||||
"NAME_IS_REQUIRED": "El nombre es obligatorio.",
|
||||
"DESCRIPTION": "Descripción",
|
||||
|
@ -292,7 +292,6 @@
|
||||
"TEST_CONNECTION_SUCCESS": "Connexion testée avec succès.",
|
||||
"TEST_CONNECTION_FAILURE": "Echec du ping du point final.",
|
||||
"NAME": "Nom",
|
||||
"STATUS": "Status",
|
||||
"PROJECT": "Projet",
|
||||
"NAME_IS_REQUIRED": "Le nom est obligatoire.",
|
||||
"DESCRIPTION": "Description",
|
||||
|
@ -311,7 +311,6 @@
|
||||
"TEST_CONNECTION_SUCCESS": "测试连接成功。",
|
||||
"TEST_CONNECTION_FAILURE": "测试连接失败。",
|
||||
"NAME": "名称",
|
||||
"STATUS": "状态",
|
||||
"PROJECT": "项目",
|
||||
"NAME_IS_REQUIRED": "名称为必填项。",
|
||||
"DESCRIPTION": "描述",
|
||||
|
1
src/ui_ng/src/images/helm-logo.svg
Normal file
1
src/ui_ng/src/images/helm-logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 33 KiB |
@ -19,7 +19,7 @@ Library OperatingSystem
|
||||
|
||||
*** Variables ***
|
||||
${HARBOR_VERSION} v1.1.1
|
||||
${CLAIR_BUILDER} 1.4.0
|
||||
${CLAIR_BUILDER} 1.6.0
|
||||
${GOLANG_VERSION} 1.9.2
|
||||
|
||||
*** Keywords ***
|
||||
|
@ -7,7 +7,17 @@ cd /harbor_src
|
||||
mv /harbor_resources/node_modules ./
|
||||
|
||||
npm install -q --no-progress
|
||||
npm run lint
|
||||
npm run lint:lib
|
||||
## Build harbor-ui and link it
|
||||
npm run build:lib
|
||||
|
||||
## Link harbor-ui
|
||||
chmod -R +xr /harbor_src/lib/dist
|
||||
cd /harbor_src/lib/dist
|
||||
npm link
|
||||
cd /harbor_src
|
||||
npm link harbor-ui
|
||||
|
||||
npm run build
|
||||
npm run test > ./npm-ut-test-results
|
||||
npm run test > ./npm-ut-test-results
|
||||
|
||||
rm -rf /harbor_src/node_modules
|
Loading…
Reference in New Issue
Block a user