mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 02:05:41 +01:00
Feature of helm chart UI
1. Add Charts list view 2. Add Charts card view 3. Add Chart version list view 4. Add chart version card view 5. Add Chart Detail Summary 6. Add Chart Detail Value 6. Add Chart Detail Deps 7. Update nodeclarity Dockerfile 8. Add markdown support 9. Add package-lock file to src
This commit is contained in:
parent
7a24dcdb05
commit
8feb49c64e
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