Files
openapi-ui/server/index.ts
2025-11-01 22:32:46 +03:00

322 lines
13 KiB
TypeScript

const path = require('path')
const fs = require('fs').promises
import express, { Express } from 'express'
import http from 'http'
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 TITLE_TEXT = process.env.LOCAL === 'true' ? options?.TITLE_TEXT : process.env.TITLE_TEXT
const LOGO_TEXT = process.env.LOCAL === 'true' ? options?.LOGO_TEXT : process.env.LOGO_TEXT
const FOOTER_TEXT = process.env.LOCAL === 'true' ? options?.FOOTER_TEXT : process.env.FOOTER_TEXT
const CUSTOM_LOGO_SVG = process.env.LOCAL === 'true' ? options?.CUSTOM_LOGO_SVG : process.env.CUSTOM_LOGO_SVG
const CUSTOM_TENANT_TEXT = process.env.LOCAL === 'true' ? options?.CUSTOM_TENANT_TEXT : process.env.CUSTOM_TENANT_TEXT
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 CUSTOMIZATION_NAVIGATION_RESOURCE_NAME =
process.env.LOCAL === 'true'
? options?.CUSTOMIZATION_NAVIGATION_RESOURCE_NAME
: process.env.CUSTOMIZATION_NAVIGATION_RESOURCE_NAME
const CUSTOMIZATION_NAVIGATION_RESOURCE =
process.env.LOCAL === 'true'
? options?.CUSTOMIZATION_NAVIGATION_RESOURCE
: process.env.CUSTOMIZATION_NAVIGATION_RESOURCE
const USE_NAMESPACE_NAV = process.env.LOCAL === 'true' ? options?.USE_NAMESPACE_NAV : process.env.USE_NAMESPACE_NAV
const NAVIGATE_FROM_CLUSTERLIST =
process.env.LOCAL === 'true' ? options?.NAVIGATE_FROM_CLUSTERLIST : process.env.NAVIGATE_FROM_CLUSTERLIST
const PROJECTS_API_GROUP = process.env.LOCAL === 'true' ? options?.PROJECTS_API_GROUP : process.env.PROJECTS_API_GROUP
const PROJECTS_VERSION = process.env.LOCAL === 'true' ? options?.PROJECTS_VERSION : process.env.PROJECTS_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_API_GROUP =
process.env.LOCAL === 'true' ? options?.INSTANCES_API_GROUP : process.env.INSTANCES_API_GROUP
const INSTANCES_VERSION = process.env.LOCAL === 'true' ? options?.INSTANCES_VERSION : process.env.INSTANCES_VERSION
const INSTANCES_RESOURCE_NAME =
process.env.LOCAL === 'true' ? options?.INSTANCES_RESOURCE_NAME : process.env.INSTANCES_RESOURCE_NAME
const BFF_URL = process.env.LOCAL === 'true' ? options?.BFF_URL : process.env.BFF_URL
const NODE_TERMINAL_DEFAULT_PROFILE =
process.env.LOCAL === 'true' ? options?.NODE_TERMINAL_DEFAULT_PROFILE : process.env.NODE_TERMINAL_DEFAULT_PROFILE
const LOGIN_URL = process.env.LOCAL === 'true' ? options?.LOGIN_URL : process.env.LOGIN_URL
const LOGOUT_URL = process.env.LOCAL === 'true' ? options?.LOGOUT_URL : process.env.LOGOUT_URL
const LOGIN_USERNAME_FIELD =
process.env.LOCAL === 'true' ? options?.LOGIN_USERNAME_FIELD : process.env.LOGIN_USERNAME_FIELD
const REMOVE_BACKLINK = process.env.LOCAL === 'true' ? options?.REMOVE_BACKLINK : process.env.REMOVE_BACKLINK
const REMOVE_BACKLINK_TEXT =
process.env.LOCAL === 'true' ? options?.REMOVE_BACKLINK_TEXT : process.env.REMOVE_BACKLINK_TEXT
const DOCS_URL = process.env.LOCAL === 'true' ? options?.DOCS_URL : process.env.DOCS_URL
const SEARCH_TABLE_CUSTOMIZATION_PREFIX =
process.env.LOCAL === 'true'
? options?.SEARCH_TABLE_CUSTOMIZATION_PREFIX
: process.env.SEARCH_TABLE_CUSTOMIZATION_PREFIX
const BASE_FACTORY_NAMESPACED_API_KEY =
process.env.LOCAL === 'true' ? options?.BASE_FACTORY_NAMESPACED_API_KEY : process.env.BASE_FACTORY_NAMESPACED_API_KEY
const BASE_FACTORY_CLUSTERSCOPED_API_KEY =
process.env.LOCAL === 'true'
? options?.BASE_FACTORY_CLUSTERSCOPED_API_KEY
: process.env.BASE_FACTORY_CLUSTERSCOPED_API_KEY
const BASE_FACTORY_NAMESPACED_BUILTIN_KEY =
process.env.LOCAL === 'true'
? options?.BASE_FACTORY_NAMESPACED_BUILTIN_KEY
: process.env.BASE_FACTORY_NAMESPACED_BUILTIN_KEY
const BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY =
process.env.LOCAL === 'true'
? options?.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY
: process.env.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY
const BASE_NAMESPACE_FACTORY_KEY =
process.env.LOCAL === 'true' ? options?.BASE_NAMESPACE_FACTORY_KEY : process.env.BASE_NAMESPACE_FACTORY_KEY
const CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP =
process.env.LOCAL === 'true'
? options?.CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
: process.env.CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
const CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION =
process.env.LOCAL === 'true'
? options?.CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
: process.env.CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
const CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME =
process.env.LOCAL === 'true'
? options?.CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
: process.env.CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
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'],
}),
)
}
const bffFormProxy =
process.env.LOCAL === 'true'
? createProxyMiddleware({
target: BFF_URL,
changeOrigin: true,
secure: false,
ws: false,
})
: undefined
// 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}`)
// },
}),
)
// Proxy: /openapi-bff
app.use(
'/openapi-bff',
createProxyMiddleware({
target: BFF_URL,
changeOrigin: true,
secure: false,
// pathRewrite: (path, req) => path.replace(/^\/bff/, ''),
// logLevel: 'debug',
// onProxyReq: (proxyReq, req, res) => {
// console.debug(`[PROXY] ${req.method} ${req.originalUrl} -> ${proxyReq.getHeader('host')}${proxyReq.path}`)
// },
}),
)
// Proxy: bffFormProxy
app.use('/openapi-bff-ws/forms', bffFormProxy)
} 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}",` : ''}
TITLE_TEXT: ${JSON.stringify(TITLE_TEXT) || '"check envs"'},
LOGO_TEXT: ${JSON.stringify(LOGO_TEXT) || '"check envs"'},
FOOTER_TEXT: ${JSON.stringify(FOOTER_TEXT) || '"check envs"'},
${CUSTOM_LOGO_SVG ? ` CUSTOM_LOGO_SVG: "${CUSTOM_LOGO_SVG}",` : ''}
${CUSTOM_TENANT_TEXT ? ` CUSTOM_TENANT_TEXT: "${CUSTOM_TENANT_TEXT}",` : ''}
CUSTOMIZATION_API_GROUP: ${JSON.stringify(CUSTOMIZATION_API_GROUP) || '"check envs"'},
CUSTOMIZATION_API_VERSION: ${JSON.stringify(CUSTOMIZATION_API_VERSION) || '"check envs"'},
CUSTOMIZATION_NAVIGATION_RESOURCE_NAME: ${
JSON.stringify(CUSTOMIZATION_NAVIGATION_RESOURCE_NAME) || '"check envs"'
},
${
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
? ` CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP: "${CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP}",`
: ''
}
${
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
? ` CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION: "${CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION}",`
: ''
}
${
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
? ` CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME: "${CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME}",`
: ''
}
CUSTOMIZATION_NAVIGATION_RESOURCE: ${JSON.stringify(CUSTOMIZATION_NAVIGATION_RESOURCE) || '"check envs"'},
USE_NAMESPACE_NAV: ${USE_NAMESPACE_NAV ? JSON.stringify(USE_NAMESPACE_NAV).toLowerCase() : '"false"'},
NAVIGATE_FROM_CLUSTERLIST: ${JSON.stringify(NAVIGATE_FROM_CLUSTERLIST) || '"check envs"'},
PROJECTS_API_GROUP: ${JSON.stringify(PROJECTS_API_GROUP) || '"check envs"'},
PROJECTS_VERSION: ${JSON.stringify(PROJECTS_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_API_GROUP: ${JSON.stringify(INSTANCES_API_GROUP) || '"check envs"'},
INSTANCES_VERSION: ${JSON.stringify(INSTANCES_VERSION) || '"check envs"'},
INSTANCES_RESOURCE_NAME: ${JSON.stringify(INSTANCES_RESOURCE_NAME) || '"check envs"'},
NODE_TERMINAL_DEFAULT_PROFILE: ${JSON.stringify(NODE_TERMINAL_DEFAULT_PROFILE) || '"general"'},
LOGIN_URL: ${JSON.stringify(LOGIN_URL) || '"check envs"'},
LOGOUT_URL: ${JSON.stringify(LOGOUT_URL) || '"check envs"'},
LOGIN_USERNAME_FIELD: ${JSON.stringify(LOGIN_USERNAME_FIELD) || '"check envs"'},
DOCS_URL: ${JSON.stringify(DOCS_URL) || '"/docs"'},
SEARCH_TABLE_CUSTOMIZATION_PREFIX: ${JSON.stringify(SEARCH_TABLE_CUSTOMIZATION_PREFIX) || '"search-"'},
REMOVE_BACKLINK: ${!!REMOVE_BACKLINK ? JSON.stringify(REMOVE_BACKLINK).toLowerCase() : '"false"'},
REMOVE_BACKLINK_TEXT: ${!!REMOVE_BACKLINK_TEXT ? JSON.stringify(REMOVE_BACKLINK_TEXT).toLowerCase() : '"false"'},
BASE_FACTORY_NAMESPACED_API_KEY: ${JSON.stringify(BASE_FACTORY_NAMESPACED_API_KEY) || '"check envs"'},
BASE_FACTORY_CLUSTERSCOPED_API_KEY: ${JSON.stringify(BASE_FACTORY_CLUSTERSCOPED_API_KEY) || '"check envs"'},
BASE_FACTORY_NAMESPACED_BUILTIN_KEY: ${JSON.stringify(BASE_FACTORY_NAMESPACED_BUILTIN_KEY) || '"check envs"'},
BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY: ${
JSON.stringify(BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY) || '"check envs"'
},
BASE_NAMESPACE_FACTORY_KEY: ${JSON.stringify(BASE_NAMESPACE_FACTORY_KEY) || '"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}`)
// })
const server = http.createServer(app)
server.listen(port, () => {
console.log(`[server]: Server is running at port: ${port}`)
})
server.on('upgrade', (req, socket, head) => {
if (process.env.LOCAL === 'true' && req.url?.indexOf('/openapi-bff-ws/forms') === 0) {
bffFormProxy.upgrade(req, socket, head)
}
})