Refactor swagger ui (#17428)

Signed-off-by: AllForNothing <sshijun@vmware.com>

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Shijun Sun 2022-08-23 12:03:45 +08:00 committed by GitHub
parent 861ca553df
commit 83bce02e61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 9788 additions and 1977 deletions

2
.gitignore vendored
View File

@ -21,6 +21,8 @@ src/portal/coverage/
src/portal/dist/
src/portal/html-report/
src/portal/node_modules/
src/portal/app-swagger-ui/node_modules/
src/portal/app-swagger-ui/webpack.dev.js
src/portal/typings/
**/*npm-debug.log.*
**/*yarn-error.log.*

View File

@ -29,9 +29,13 @@ RUN python -c 'import sys, yaml, json; y=yaml.load(sys.stdin.read()); print json
RUN cp swagger.yaml dist
COPY ./LICENSE /build_dir/dist
RUN cd app-swagger-ui && npm install --unsafe-perm
RUN cd app-swagger-ui && npm run build
FROM ${harbor_base_namespace}/harbor-portal-base:${harbor_base_image_version}
COPY --from=nodeportal /build_dir/dist /usr/share/nginx/html
COPY --from=nodeportal /build_dir/app-swagger-ui/dist /usr/share/nginx/html
COPY --from=nodeportal /build_dir/package*.json /usr/share/nginx/
VOLUME /var/cache/nginx /var/log/nginx /run

View File

@ -40,6 +40,10 @@ http {
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
location /devcenter-api-2.0 {
try_files $uri $uri/ /swagger-ui-index.html;
}
location / {
try_files $uri $uri/ /index.html;
}
@ -48,4 +52,4 @@ http {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
}
}
}

View File

@ -1,7 +1,8 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
"projects/**/*",
"**/*.js"
],
"overrides": [
{

View File

@ -17,6 +17,8 @@
"no-empty-source": null
},
"ignoreFiles": [
"src/**/*.html"
"**/*.md",
"src/**/*.html",
"app-swagger-ui/**/*.html"
]
}

View File

@ -1,7 +1,7 @@
```text
{
"name": "harbor",
"version": "2.5.0",
"version": "2.7.0",
"description": "Harbor UI with Clarity",
"angular-cli": {},
"scripts": {
@ -54,16 +54,16 @@
"@angular/localize": "~14.1.0",
"@angular/platform-browser": "~14.1.0",
"@angular/platform-browser-dynamic": "~14.1.0",
"@angular/router": "~13.2.2",
"@angular/router": "~14.1.0",
"rxjs": "^7.4.0",
"tslib": "^2.2.0",
"zone.js": "~0.11.4",
// Clarity UI. Required
"@clr/angular": "13.0.2",
"@cds/core": "5.6.4",
"@clr/angular": "13.7.0",
"@cds/core": "6.0.0",
"@clr/icons": "13.0.2",
"@clr/ui": "13.0.2",
"@clr/ui": "13.7.0",
// For Harbor i18n functionality. Required
"@ngx-translate/core": "^13.0.0",
@ -81,11 +81,6 @@
// To render markdown data. Required
"ngx-markdown": "~13.0.0",
// For swagger API center. Required
"swagger-ui": "~4.9.0",
"buffer": "^6.0.3",
"stream-browserify": "^3.0.0",
// To convert yaml to json. Required
"js-yaml": "^4.1.0"
},

View File

@ -76,6 +76,16 @@ Start
"secure": false,
"logLevel": "debug"
},
"/chartrepo/*": {
"target": "https://hostname",
"secure": false,
"logLevel": "debug"
},
"/LICENSE": {
"target": "https://hostname",
"secure": false,
"logLevel": "debug"
},
"/swagger.json": {
"target": "https://hostname",
"secure": false,
@ -86,12 +96,12 @@ Start
"secure": false,
"logLevel": "debug"
},
"/chartrepo/*": {
"/devcenter-api-2.0": {
"target": "https://hostname",
"secure": false,
"logLevel": "debug"
},
"/LICENSE": {
"/swagger-ui.bundle.js": {
"target": "https://hostname",
"secure": false,
"logLevel": "debug"

View File

@ -12,20 +12,8 @@
"builder": "@angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": [
"swagger-ui",
"buffer",
"js-yaml",
"hoist-non-react-statics",
"lodash",
"core-js-pure",
"prop-types",
"randexp",
"react-copy-to-clipboard",
"react-debounce-input",
"react-immutable-proptypes",
"redux-immutable",
"url-parse",
"xml-but-prettier"
"cron-validator",
"js-yaml"
],
"outputPath": "dist",
"index": "src/index.html",
@ -41,7 +29,6 @@
"styles": [
"node_modules/@clr/icons/clr-icons.min.css",
"node_modules/@clr/ui/clr-ui.min.css",
"node_modules/swagger-ui/dist/swagger-ui.css",
"node_modules/prismjs/themes/prism-solarizedlight.css",
"src/global.scss",
{

View File

@ -0,0 +1,15 @@
Swagger UI
============
This is the project based on Swagger UI and Webpack.
Start
============
1. npm install
2. change `webpack.dev.js.temp` to `webpack.dev.js`,
```shell
mv webpack.dev.js.temp webpack.dev.js
```
3. modify `webpack.dev.js`, replace `https://example.com` with an available Harbor server
4. npm run start

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Harbor</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico?v=2" />
<style>
.spinner {
position: absolute;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: inline-block;
width: 108px;
height: 108px;
animation: spin 1s linear infinite;
background: url(data:image/svg+xml;charset=utf8,%3Csvg%20id%3D%22Layer_2%22%20data-name%3D%22Layer%202%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2072%2072%22%3E%0A%20%20%20%20%3Cdefs%3E%0A%20%20%20%20%20%20%20%20%3Cstyle%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20.cls-1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20.cls-2%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fill%3A%20none%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stroke-miterlimit%3A%2010%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stroke-width%3A%205px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20.cls-1%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stroke%3A%20%23000000%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stroke-opacity%3A%200.15%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20.cls-2%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stroke%3A%20%230072a3%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%3C%2Fstyle%3E%0A%20%20%20%20%3C%2Fdefs%3E%0A%20%20%20%20%3Ctitle%3EPreloader_72x2%3C%2Ftitle%3E%0A%20%20%20%20%3Ccircle%20class%3D%22cls-1%22%20cx%3D%2236%22%20cy%3D%2236%22%20r%3D%2233%22%2F%3E%0A%20%20%20%20%3Cpath%20class%3D%22cls-2%22%20d%3D%22M14.3%2C60.9A33%2C33%2C0%2C0%2C1%2C36%2C3%22%3E%0A%20%20%20%20%3C%2Fpath%3E%0A%3C%2Fsvg%3E%0A);
text-indent: 100%;
overflow: hidden;
white-space: nowrap;
}
@keyframes spin {
0% {
transform: rotateZ(0);
}
100% {
transform: rotateZ(360deg);
}
}
</style>
</head>
<body>
<div id="swagger-ui-container" class="spinner">Loading...</div>
</body>
</html>

9362
src/portal/app-swagger-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
{
"name": "swagger-ui",
"version": "2.7.0",
"description": "Swagger UI for Harbor APIs",
"scripts": {
"build": "webpack --config webpack.prod.js && mv dist/index.html dist/swagger-ui-index.html",
"start": "webpack serve --open --config webpack.dev.js"
},
"dependencies": {
"css-loader": "^6.7.1",
"style-loader": "^3.3.1",
"swagger-ui": "4.13.2"
},
"devDependencies": {
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"html-webpack-plugin": "^5.5.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.10.0"
}
}

View File

@ -0,0 +1,93 @@
import SwaggerUI from 'swagger-ui'
import 'swagger-ui/dist/swagger-ui.css';
const helpInfo =
' If you want to enable basic authorization,' +
' please logout Harbor first or manually delete the cookies under the current domain.';
const SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS', 'TRACE'];
// get swagger.json and swagger2.json from portal container then render swagger ui
// before rendering, the ui shows a loading style
Promise.all([
fetch('/swagger.json').then(value => value.json()),
fetch('/swagger2.json').then(value => value.json())
])
.then(value => {
// merger swagger.json and swagger2.json
const json = {};
mergeDeep(json, value[0], value[1]);
json['host'] = window.location.host;
const protocal = window.location.protocol;
json['schemes'] = [protocal.replace(':', '')];
json.info.description = json.info.description + helpInfo;
// start to render
SwaggerUI({
spec: json,
dom_id: '#swagger-ui-container',
deepLinking: true,
presets: [SwaggerUI.presets.apis],
requestInterceptor: request => {
// Get the csrf token from localstorage
const token = localStorage.getItem('__csrf');
const headers = request.headers || {};
if (token) {
if (
request.method &&
SAFE_METHODS.indexOf(
request.method.toUpperCase()
) === -1
) {
headers['X-Harbor-CSRF-Token'] = token;
}
}
return request;
},
responseInterceptor: response => {
const headers = response.headers || {};
const responseToken =
headers['X-Harbor-CSRF-Token'];
if (responseToken) {
// Set the csrf token to localstorage
localStorage.setItem('__csrf', responseToken);
}
return response;
},
});
// remove loading style
document.getElementById('swagger-ui-container').removeAttribute('class');
})
.catch((err) => {
console.error(err);
});
function mergeDeep(target, ...sources) {
if (!sources.length) {
return target;
}
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) {
Object.assign(target, { [key]: {} });
}
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}

View File

@ -0,0 +1,52 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const outputPath = path.resolve(__dirname, 'dist');
module.exports = {
devServer: {
proxy: {
'/swagger.json': {
target: 'https://example.com',
"secure": false,
},
'/swagger2.json': {
target: 'https://example.com',
"secure": false,
}
}
},
mode: 'development',
entry: {
app: require.resolve('./src/index'),
},
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
]
}
]
},
plugins: [
new CopyPlugin({
patterns: [
`favicon.ico`
]
}),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'index.html'
})
],
output: {
filename: 'swagger-ui.bundle.js',
path: outputPath,
}
};

View File

@ -0,0 +1,32 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const outputPath = path.resolve(__dirname, 'dist');
module.exports = {
mode: 'production',
entry: {
app: require.resolve('./src/index'),
},
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'index.html'
})
],
output: {
filename: 'swagger-ui.bundle.js',
path: outputPath,
}
};

View File

@ -19,10 +19,15 @@ RUN python -c 'import sys, yaml, json; y=yaml.load(sys.stdin.read()); print json
COPY LICENSE /build_dir/dist
RUN cd app-swagger-ui && npm install --unsafe-perm
RUN cd app-swagger-ui && npm run build
FROM nginx:1.17
COPY --from=builder /build_dir/dist /usr/share/nginx/html
COPY --from=builder /build_dir/app-swagger-ui/dist /usr/share/nginx/html
COPY src/portal/docker-build/nginx.conf /etc/nginx/nginx.conf
EXPOSE 8080

View File

@ -26,6 +26,10 @@ http {
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
location /devcenter-api-2.0 {
try_files $uri $uri/ /swagger-ui-index.html;
}
location / {
try_files $uri $uri/ /index.html;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "harbor",
"version": "2.6.0",
"version": "2.7.0",
"description": "Harbor UI with Clarity",
"angular-cli": {},
"scripts": {
@ -42,15 +42,12 @@
"@clr/ui": "13.7.0",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"buffer": "^6.0.3",
"cron-validator": "^1.2.1",
"cron-validator": "^1.3.1",
"js-yaml": "^4.1.0",
"ngx-clipboard": "^12.3.1",
"ngx-cookie": "^5.0.2",
"ngx-clipboard": "^15.1.0",
"ngx-cookie": "^6.0.1",
"ngx-markdown": "14.0.1",
"rxjs": "^7.4.0",
"stream-browserify": "^3.0.0",
"swagger-ui": "~4.10.3",
"tslib": "^2.2.0",
"zone.js": "~0.11.4"
},

View File

@ -1,25 +0,0 @@
import { AfterViewInit, Component, Directive, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { CookieService } from 'ngx-cookie';
@Directive()
export abstract class DevCenterBaseDirective implements OnInit, AfterViewInit {
protected constructor(
public translate: TranslateService,
public cookieService: CookieService,
public titleService: Title
) {}
ngOnInit() {
this.setTitle('APP_TITLE.HARBOR_SWAGGER');
}
private setTitle(key: string) {
this.translate.get(key).subscribe((res: string) => {
this.titleService.setTitle(res);
});
}
abstract getSwaggerUI();
abstract ngAfterViewInit();
}

View File

@ -1 +0,0 @@
<div class="swagger-container"></div>

View File

@ -1,8 +0,0 @@
.swagger-container {
overflow: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

View File

@ -1,49 +0,0 @@
import { ComponentFixture, TestBed, getTestBed } from '@angular/core/testing';
import { HttpTestingController } from '@angular/common/http/testing';
import { DevCenterComponent } from './dev-center.component';
import { CookieService } from 'ngx-cookie';
import { SharedTestingModule } from '../shared/shared.module';
describe('DevCenterComponent', () => {
let component: DevCenterComponent;
let fixture: ComponentFixture<DevCenterComponent>;
const mockCookieService = {
get: () => {
return 'xsrf';
},
};
let cookie = 'fdsa|ds';
let injector: TestBed;
let httpMock: HttpTestingController;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DevCenterComponent],
imports: [SharedTestingModule],
providers: [
{
provide: CookieService,
useValue: mockCookieService,
},
],
}).compileComponents();
injector = getTestBed();
httpMock = injector.inject(HttpTestingController);
});
beforeEach(() => {
fixture = TestBed.createComponent(DevCenterComponent);
component = fixture.componentInstance;
fixture.autoDetectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('get swagger should return data', () => {
const req = httpMock.expectOne('/swagger.json');
expect(req.request.method).toBe('GET');
req.flush({
host: '122.33',
});
});
});

View File

@ -1,96 +0,0 @@
import { AfterViewInit, Component, ElementRef, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { throwError as observableThrowError, forkJoin } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { CookieService } from 'ngx-cookie';
import SwaggerUI from 'swagger-ui';
import { mergeDeep } from '../shared/units/utils';
import { DevCenterBaseDirective } from './dev-center-base';
import { SAFE_METHODS } from '../services/intercept-http.service';
enum SwaggerJsonUrls {
SWAGGER1 = '/swagger.json',
SWAGGER2 = '/swagger2.json',
}
const helpInfo: string =
' If you want to enable basic authorization,' +
' please logout Harbor first or manually delete the cookies under the current domain.';
@Component({
selector: 'dev-center',
templateUrl: 'dev-center.component.html',
viewProviders: [Title],
styleUrls: ['dev-center.component.scss'],
})
export class DevCenterComponent
extends DevCenterBaseDirective
implements AfterViewInit, OnInit
{
private ui: any;
constructor(
private el: ElementRef,
private http: HttpClient,
public translate: TranslateService,
public cookieService: CookieService,
public titleService: Title
) {
super(translate, cookieService, titleService);
}
ngAfterViewInit() {
this.getSwaggerUI();
}
getSwaggerUI() {
forkJoin([
this.http.get(SwaggerJsonUrls.SWAGGER1),
this.http.get(SwaggerJsonUrls.SWAGGER2),
])
.pipe(catchError(error => observableThrowError(error)))
.subscribe(jsonArr => {
const json: any = {};
mergeDeep(json, jsonArr[0], jsonArr[1]);
json['host'] = window.location.host;
const protocal = window.location.protocol;
json['schemes'] = [protocal.replace(':', '')];
json.info.description = json.info.description + helpInfo;
this.ui = SwaggerUI({
spec: json,
domNode:
this.el.nativeElement.querySelector(
'.swagger-container'
),
deepLinking: true,
presets: [SwaggerUI.presets.apis],
requestInterceptor: request => {
// Get the csrf token from localstorage
const token = localStorage.getItem('__csrf');
const headers = request.headers || {};
if (token) {
if (
request.method &&
SAFE_METHODS.indexOf(
request.method.toUpperCase()
) === -1
) {
headers['X-Harbor-CSRF-Token'] = token;
}
}
return request;
},
responseInterceptor: response => {
const headers = response.headers || {};
const responseToken: string =
headers['X-Harbor-CSRF-Token'];
if (responseToken) {
// Set the csrf token to localstorage
localStorage.setItem('__csrf', responseToken);
}
return response;
},
});
});
}
}

View File

@ -1,15 +0,0 @@
import { NgModule } from '@angular/core';
import { DevCenterComponent } from './dev-center.component';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: '',
component: DevCenterComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
declarations: [DevCenterComponent],
})
export class DeveloperCenterModule {}

View File

@ -30,13 +30,6 @@ const harborRoutes: Routes = [
loadChildren: () =>
import('./account/account.module').then(m => m.AccountModule),
},
{
path: 'devcenter-api-2.0',
loadChildren: () =>
import('./dev-center/dev-center.module').then(
m => m.DeveloperCenterModule
),
},
{
path: 'oidc-onboard',
canActivate: [OidcGuard, SignInGuard],

View File

@ -79,6 +79,3 @@ import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
(window as any).global = window; // this is for swagger UI
// @ts-ignore
window.Buffer = window.Buffer || require('buffer').Buffer; // this is for swagger UI

View File

@ -2,9 +2,6 @@
{
"compileOnSave": false,
"compilerOptions": {
"paths": {
"stream": [ "./node_modules/stream-browserify" ]
},
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,