mirror of
https://github.com/outbackdingo/openapi-ui.git
synced 2026-01-27 10:19:49 +00:00
Merge pull request #8 from PRO-Robotech/feature/Design
serving with express
This commit is contained in:
@@ -1 +1,8 @@
|
||||
KUBE_API_URL=
|
||||
CUSTOMIZATION_API_GROUP=
|
||||
CUSTOMIZATION_API_VERSION=
|
||||
RPROJECTS_VERSION=
|
||||
PROJECTS_RESOURCE_NAME=
|
||||
MARKETPLACE_RESOURCE_NAME=
|
||||
MARKETPLACE_KIND=
|
||||
INSTANCES_VERSION=
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
**/build/*
|
||||
**/public/*
|
||||
**/server/*
|
||||
**/node_modules/*
|
||||
!src/
|
||||
vite.config.ts
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
server/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
@@ -11,10 +12,11 @@
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.options
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.env.options
|
||||
.env.local
|
||||
public/env.js
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script src="/env.js"></script>
|
||||
<title>OpenAPI UI</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
1695
package-lock.json
generated
1695
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "openapi-ui",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -8,6 +7,10 @@
|
||||
"build": "tsc && vite build",
|
||||
"serve": "vite preview --port 4001 --strictPort",
|
||||
"preview": "vite preview --port 4001 --strictPort",
|
||||
"server:prod": "node build/index.js",
|
||||
"server:prod:local": "cross-env LOCAL=true BASEPREFIX=/openapi-ui node build/index.js",
|
||||
"server:dev": "cross-env LOCAL=true BASEPREFIX=/openapi-ui tsx ./server/index.ts",
|
||||
"server:build": "tsc --project tsconfig.server.json --noEmit false",
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
"lint:css": "stylelint ./src/**/styled.{js,ts} --fix",
|
||||
"lint:js": "eslint .",
|
||||
@@ -17,7 +20,7 @@
|
||||
"@ant-design/icons": "5.6.0",
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
"@originjs/vite-plugin-federation": "1.3.6",
|
||||
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.23",
|
||||
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.24",
|
||||
"@readme/openapi-parser": "4.0.0",
|
||||
"@reduxjs/toolkit": "2.2.5",
|
||||
"@tanstack/react-query": "5.62.2",
|
||||
@@ -25,10 +28,16 @@
|
||||
"antd": "5.20.0",
|
||||
"axios": "1.4.0",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.7",
|
||||
"dotenv": "16.4.5",
|
||||
"express": "4.19.2",
|
||||
"express-healthcheck": "0.1.0",
|
||||
"express-prom-bundle": "8.0.0",
|
||||
"express-winston": "4.2.0",
|
||||
"http-proxy-middleware": "2.0.6",
|
||||
"jsonpath": "1.1.1",
|
||||
"lodash": "4.17.21",
|
||||
"openapi-types": "12.1.3",
|
||||
"prom-client": "15.1.3",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-redux": "9.1.2",
|
||||
@@ -63,6 +72,7 @@
|
||||
"prettier": "3.0.2",
|
||||
"stylelint": "16.14.1",
|
||||
"stylelint-config-standard": "37.0.0",
|
||||
"tsx": "4.19.2",
|
||||
"vite": "5.4.6",
|
||||
"vite-plugin-node-polyfills": "0.22.0",
|
||||
"vite-tsconfig-paths": "5.0.1"
|
||||
|
||||
31
server/getDynamicIndex.ts
Normal file
31
server/getDynamicIndex.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const getDynamicIndex = (baseprefix: string): string => {
|
||||
try {
|
||||
const mainJs = 'index-react.js'
|
||||
const mainCss = 'style.css'
|
||||
|
||||
return `<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>OpenAPI UI</title>
|
||||
<script src="${baseprefix}/env.js"></script>
|
||||
<script type="module" crossorigin src="${baseprefix}/${mainJs}"></script>
|
||||
<link rel="stylesheet" crossorigin href="${baseprefix}/${mainCss}">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
} catch {
|
||||
return 'Error while trying'
|
||||
}
|
||||
}
|
||||
158
server/index.ts
Normal file
158
server/index.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
import express, { Express } from 'express'
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware')
|
||||
import dotenv from 'dotenv'
|
||||
import { getDynamicIndex } from './getDynamicIndex'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const basePrefix = process.env.BASEPREFIX
|
||||
|
||||
let options: dotenv.DotenvParseOutput | undefined
|
||||
if (process.env.LOCAL === 'true') {
|
||||
const { parsed } = dotenv.config({ path: './.env.options' })
|
||||
options = parsed
|
||||
}
|
||||
|
||||
const KUBE_API_URL = process.env.LOCAL === 'true' ? options?.KUBE_API_URL : process.env.KUBE_API_URL
|
||||
const CUSTOMIZATION_API_GROUP =
|
||||
process.env.LOCAL === 'true' ? options?.CUSTOMIZATION_API_GROUP : process.env.CUSTOMIZATION_API_GROUP
|
||||
const CUSTOMIZATION_API_VERSION =
|
||||
process.env.LOCAL === 'true' ? options?.CUSTOMIZATION_API_VERSION : process.env.CUSTOMIZATION_API_VERSION
|
||||
const RPROJECTS_VERSION = process.env.LOCAL === 'true' ? options?.RPROJECTS_VERSION : process.env.RPROJECTS_VERSION
|
||||
const PROJECTS_RESOURCE_NAME =
|
||||
process.env.LOCAL === 'true' ? options?.PROJECTS_RESOURCE_NAME : process.env.PROJECTS_RESOURCE_NAME
|
||||
const MARKETPLACE_RESOURCE_NAME =
|
||||
process.env.LOCAL === 'true' ? options?.MARKETPLACE_RESOURCE_NAME : process.env.MARKETPLACE_RESOURCE_NAME
|
||||
const MARKETPLACE_KIND = process.env.LOCAL === 'true' ? options?.MARKETPLACE_KIND : process.env.MARKETPLACE_KIND
|
||||
const INSTANCES_VERSION = process.env.LOCAL === 'true' ? options?.INSTANCES_VERSION : process.env.INSTANCES_VERSION
|
||||
|
||||
const healthcheck = require('express-healthcheck')
|
||||
const promBundle = require('express-prom-bundle')
|
||||
|
||||
const metricsMiddleware = promBundle({ includeMethod: true, metricsPath: `${basePrefix ? basePrefix : ''}/metrics` })
|
||||
const winston = require('winston')
|
||||
const expressWinston = require('express-winston')
|
||||
|
||||
const app: Express = express()
|
||||
const port = process.env.PORT || 8080
|
||||
|
||||
app.use(`${basePrefix ? basePrefix : ''}/healthcheck`, healthcheck())
|
||||
app.use(metricsMiddleware)
|
||||
|
||||
if (process.env.LOGGER === 'true') {
|
||||
app.use(
|
||||
expressWinston.logger({
|
||||
transports: [new winston.transports.Console()],
|
||||
timeStamp: true,
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
}),
|
||||
winston.format.json(),
|
||||
),
|
||||
expressFormat: true,
|
||||
colorize: false,
|
||||
requestWhitelist: ['body'],
|
||||
responseWhitelist: ['body'],
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Only add proxies if LOCAL=true
|
||||
if (process.env.LOCAL === 'true') {
|
||||
console.log('✅ Proxies are enabled.')
|
||||
// Proxy: /api/clusters/.*/k8s/
|
||||
app.use(
|
||||
'/api/clusters/:clusterId/k8s',
|
||||
createProxyMiddleware({
|
||||
target: `${KUBE_API_URL}/api/clusters`,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
ws: true,
|
||||
pathRewrite: (path, req) => path.replace(/^\/api\/clusters\//, '/'),
|
||||
// logLevel: 'debug',
|
||||
// onProxyReq: (proxyReq, req, res) => {
|
||||
// console.debug(`[PROXY] ${req.method} ${req.originalUrl} -> ${proxyReq.getHeader('host')}${proxyReq.path}`)
|
||||
// },
|
||||
}),
|
||||
)
|
||||
|
||||
// Proxy: /clusterlist
|
||||
app.use(
|
||||
'/clusterlist',
|
||||
createProxyMiddleware({
|
||||
target: `${KUBE_API_URL}/clusterlist`,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
pathRewrite: (path, req) => path.replace(/^\/clusterlist/, ''),
|
||||
// logLevel: 'debug',
|
||||
// onProxyReq: (proxyReq, req, res) => {
|
||||
// console.debug(`[PROXY] ${req.method} ${req.originalUrl} -> ${proxyReq.getHeader('host')}${proxyReq.path}`)
|
||||
// },
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
console.log('🚫 Proxies are disabled.')
|
||||
}
|
||||
|
||||
app.get(`${basePrefix ? basePrefix : ''}/env.js`, (_, res) => {
|
||||
res.set('Content-Type', 'text/javascript')
|
||||
res.send(
|
||||
`
|
||||
window._env_ = {
|
||||
${basePrefix ? ` BASEPREFIX: "${basePrefix}",` : ''}
|
||||
CUSTOMIZATION_API_GROUP: ${JSON.stringify(CUSTOMIZATION_API_GROUP) || '"check envs"'},
|
||||
CUSTOMIZATION_API_VERSION: ${JSON.stringify(CUSTOMIZATION_API_VERSION) || '"check envs"'},
|
||||
RPROJECTS_VERSION: ${JSON.stringify(RPROJECTS_VERSION) || '"check envs"'},
|
||||
PROJECTS_RESOURCE_NAME: ${JSON.stringify(PROJECTS_RESOURCE_NAME) || '"check envs"'},
|
||||
MARKETPLACE_RESOURCE_NAME: ${JSON.stringify(MARKETPLACE_RESOURCE_NAME) || '"check envs"'},
|
||||
MARKETPLACE_KIND: ${JSON.stringify(MARKETPLACE_KIND) || '"check envs"'},
|
||||
INSTANCES_VERSION: ${JSON.stringify(INSTANCES_VERSION) || '"check envs"'}
|
||||
}
|
||||
`,
|
||||
)
|
||||
})
|
||||
|
||||
app.get(`${basePrefix ? basePrefix : ''}/docs`, (_, res) => {
|
||||
res.redirect(process.env.DOCUMENTATION_URI || '/')
|
||||
})
|
||||
|
||||
const tryFiles = async (req, res, next) => {
|
||||
try {
|
||||
const unsafeReqPath = basePrefix ? req.path.replace(basePrefix, '') : req.path
|
||||
const safeReqPath = path.normalize(unsafeReqPath).replace(/^(\.\.(\/|\\|$))+/, '')
|
||||
const filePath = path.join(__dirname, safeReqPath.replace(/^\//, ''))
|
||||
await fs.access(filePath)
|
||||
return res.sendFile(filePath)
|
||||
} catch (error: any) {
|
||||
if (basePrefix) {
|
||||
const indexText = getDynamicIndex(basePrefix)
|
||||
res.set('Content-Type', 'text/html')
|
||||
return res.send(indexText)
|
||||
}
|
||||
return res.sendFile('/index.html', {
|
||||
root: path.join(__dirname),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
app.get(`${basePrefix ? basePrefix : ''}/`, (_, res) => {
|
||||
if (basePrefix) {
|
||||
const indexText = getDynamicIndex(basePrefix)
|
||||
res.set('Content-Type', 'text/html')
|
||||
return res.send(indexText)
|
||||
}
|
||||
res.sendFile('/index.html', {
|
||||
root: path.join(__dirname),
|
||||
})
|
||||
})
|
||||
|
||||
app.get('*', (req, res, next) => {
|
||||
tryFiles(req, res, next)
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on http://localhost:${port}`)
|
||||
})
|
||||
2184
server/package-lock.json
generated
Normal file
2184
server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
server/package.json
Normal file
22
server/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "openapi-ui",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"server:build": "tsc --project tsconfig.server.json --noEmit false"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.5",
|
||||
"express": "4.19.2",
|
||||
"express-healthcheck": "0.1.0",
|
||||
"express-prom-bundle": "8.0.0",
|
||||
"express-winston": "4.2.0",
|
||||
"http-proxy-middleware": "2.0.6",
|
||||
"prom-client": "15.1.3",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "4.19.2"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
export const BASE_API_GROUP = import.meta.env.VITE_CUSTOMIZATION_API_GROUP
|
||||
export const BASE_API_VERSION = import.meta.env.VITE_CUSTOMIZATION_API_VERSION
|
||||
export const BASE_PROJECTS_VERSION = import.meta.env.VITE_PROJECTS_VERSION
|
||||
export const BASE_PROJECTS_RESOURCE_NAME = import.meta.env.VITE_PROJECTS_RESOURCE_NAME
|
||||
export const BASE_MARKETPLACE_RESOURCE_NAME = import.meta.env.VITE_MARKETPLACE_RESOURCE_NAME
|
||||
export const BASE_MARKETPLACE_KIND = import.meta.env.VITE_MARKETPLACE_KIND
|
||||
export const BASE_INSTANCES_VERSION = import.meta.env.VITE_INSTANCES_VERSION
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export const BASE_API_GROUP = window._env_.CUSTOMIZATION_API_GROUP || import.meta.env.VITE_CUSTOMIZATION_API_GROUP
|
||||
export const BASE_API_VERSION = window._env_.CUSTOMIZATION_API_VERSION || import.meta.env.VITE_CUSTOMIZATION_API_VERSION
|
||||
export const BASE_PROJECTS_VERSION = window._env_.PROJECTS_VERSION || import.meta.env.VITE_PROJECTS_VERSION
|
||||
export const BASE_PROJECTS_RESOURCE_NAME =
|
||||
window._env_.PROJECTS_RESOURCE_NAME || import.meta.env.VITE_PROJECTS_RESOURCE_NAME
|
||||
export const BASE_MARKETPLACE_RESOURCE_NAME =
|
||||
window._env_.MARKETPLACE_RESOURCE_NAME || import.meta.env.VITE_MARKETPLACE_RESOURCE_NAME
|
||||
export const BASE_MARKETPLACE_KIND = window._env_.MARKETPLACE_KIND || import.meta.env.VITE_MARKETPLACE_KIND
|
||||
export const BASE_INSTANCES_VERSION = window._env_.INSTANCES_VERSION || import.meta.env.VITE_INSTANCES_VERSION
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export const getBasePrefix = (isFederation?: boolean) => {
|
||||
if (isFederation) {
|
||||
return '/openapi-ui-federation'
|
||||
}
|
||||
return import.meta.env.BASE_URL || '/openapi-ui'
|
||||
return window._env_.BASEPREFIX || import.meta.env.BASE_URL || '/openapi-ui'
|
||||
}
|
||||
|
||||
23
tsconfig.server.json
Normal file
23
tsconfig.server.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "server",
|
||||
"target": "esnext",
|
||||
"lib": ["esnext"],
|
||||
"module": "CommonJS",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"outDir": "./build",
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"include": ["server", "server/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -5,19 +5,26 @@ import react from '@vitejs/plugin-react-swc'
|
||||
import federation from '@originjs/vite-plugin-federation'
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||
|
||||
const { VITE_BASEPREFIX } = process.env
|
||||
const { parsed: options } = await dotenv.config({ path: './.env.options' })
|
||||
// const { VITE_BASEPREFIX } = process.env
|
||||
const { parsed: options } = dotenv.config({ path: './.env.options' })
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
root: './',
|
||||
base: VITE_BASEPREFIX || '/openapi-ui',
|
||||
// base: VITE_BASEPREFIX || '/openapi-ui',
|
||||
build: {
|
||||
outDir: 'build',
|
||||
modulePreload: false,
|
||||
target: 'esnext',
|
||||
minify: false,
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `[name]-react.js`,
|
||||
chunkFileNames: `[name]-react.js`,
|
||||
assetFileNames: `[name].[ext]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
publicDir: 'public',
|
||||
plugins: [
|
||||
|
||||
Reference in New Issue
Block a user