mirror of
https://github.com/outbackdingo/openapi-ui.git
synced 2026-01-27 10:19:49 +00:00
pod terminal
This commit is contained in:
33
package-lock.json
generated
33
package-lock.json
generated
@@ -11,13 +11,14 @@
|
||||
"@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.62",
|
||||
"@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.63",
|
||||
"@readme/openapi-parser": "4.0.0",
|
||||
"@reduxjs/toolkit": "2.2.5",
|
||||
"@tanstack/react-query": "5.62.2",
|
||||
"@tanstack/react-query-devtools": "5.62.2",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"@xterm/addon-attach": "0.11.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "5.5.0",
|
||||
"antd": "5.26.4",
|
||||
"axios": "1.4.0",
|
||||
"cross-env": "7.0.3",
|
||||
@@ -39,6 +40,7 @@
|
||||
"typescript": "4.9.5",
|
||||
"usehooks-ts": "3.1.1",
|
||||
"uuid": "11.0.3",
|
||||
"xterm-theme": "1.1.0",
|
||||
"yaml": "2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -2800,13 +2802,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prorobotech/openapi-k8s-toolkit": {
|
||||
"version": "0.0.1-alpha.62",
|
||||
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.62.tgz",
|
||||
"integrity": "sha512-Ase/PSMID8NKuZhNMn19f4rWluKlaAA62YOxY7CJq0ji1DP4hQuw3Izw8Qm/CFAMJ/J9wdnyEgFraEZ9qAeP+g==",
|
||||
"version": "0.0.1-alpha.63",
|
||||
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.63.tgz",
|
||||
"integrity": "sha512-eLrSeLkswEE287T3U0sGm1UhoiVJrSvZdx6DsIfOxoKYfnpa8bMArh9/baNOSc2f/vivzn+eksuCfm4No0N5yA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
"@reduxjs/toolkit": "2.2.5",
|
||||
"@xterm/addon-attach": "0.11.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "5.5.0",
|
||||
"axios": "1.4.0",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.7",
|
||||
@@ -2818,6 +2823,7 @@
|
||||
"typescript": "4.9.5",
|
||||
"usehooks-ts": "3.1.1",
|
||||
"uuid": "11.0.3",
|
||||
"xterm-theme": "1.1.0",
|
||||
"yaml": "2.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -4438,6 +4444,15 @@
|
||||
"vite": "^4 || ^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-attach": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-attach/-/addon-attach-0.11.0.tgz",
|
||||
"integrity": "sha512-JboCN0QAY6ZLY/SSB/Zl2cQ5zW1Eh4X3fH7BnuR1NB7xGRhzbqU2Npmpiw/3zFlxDaU88vtKzok44JKi2L2V2Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-fit": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
|
||||
@@ -13529,6 +13544,12 @@
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/xterm-theme": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/xterm-theme/-/xterm-theme-1.1.0.tgz",
|
||||
"integrity": "sha512-n2GocBEbqcz4vEl4OYkU93hEVia8GWdnqchiz/0nQ/olRUyhulGf4wfha23x/D2m0imWaIavRZtt8c6kWZXdsA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
||||
@@ -20,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.62",
|
||||
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.63",
|
||||
"@readme/openapi-parser": "4.0.0",
|
||||
"@reduxjs/toolkit": "2.2.5",
|
||||
"@tanstack/react-query": "5.62.2",
|
||||
@@ -49,6 +49,7 @@
|
||||
"typescript": "4.9.5",
|
||||
"usehooks-ts": "3.1.1",
|
||||
"uuid": "11.0.3",
|
||||
"xterm-theme": "1.1.0",
|
||||
"yaml": "2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'store/store'
|
||||
import { BASE_API_GROUP, BASE_API_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
import { HEAD_FIRST_ROW, HEAD_SECOND_ROW, FOOTER_HEIGHT, NAV_HEIGHT } from 'constants/blocksSizes'
|
||||
import { TestWs } from './TestWs'
|
||||
import '@xterm/xterm/css/xterm.css'
|
||||
|
||||
type TFactoryProps = {
|
||||
setSidebarTags: (tags: string[]) => void
|
||||
@@ -60,7 +60,6 @@ export const Factory: FC<TFactoryProps> = ({ setSidebarTags }) => {
|
||||
if (spec.withScrollableMainContentCard) {
|
||||
return (
|
||||
<ContentCard flexGrow={1} displayFlex flexFlow="column" maxHeight={height}>
|
||||
<TestWs />
|
||||
<DynamicRendererWithProviders
|
||||
urlsToFetch={spec.urlsToFetch}
|
||||
theme={theme}
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import React, { FC, useEffect, useState, useRef } from 'react'
|
||||
import { Result, Spin } from 'antd'
|
||||
import { Terminal as XTerm } from '@xterm/xterm'
|
||||
import { FitAddon } from '@xterm/addon-fit'
|
||||
import '@xterm/xterm/css/xterm.css'
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { RootState } from 'store/store'
|
||||
import { Styled } from './styled'
|
||||
|
||||
type TXTerminalProps = {
|
||||
endpoint: string
|
||||
namespace: string
|
||||
podName: string
|
||||
}
|
||||
|
||||
export const XTerminal: FC<TXTerminalProps> = ({ endpoint, namespace, podName }) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||
const [error, setError] = useState<Event>()
|
||||
const [terminal, setTerminal] = useState<XTerm>()
|
||||
const terminalRef = useRef<HTMLDivElement>(null)
|
||||
const resizeObserverRef = useRef<ResizeObserver | null>(null)
|
||||
const fitAddon = new FitAddon()
|
||||
|
||||
// const decoderRef = useRef(new TextDecoder('utf-8'))
|
||||
|
||||
useEffect(() => {
|
||||
const terminal = new XTerm({
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'block',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 10,
|
||||
})
|
||||
terminal.loadAddon(fitAddon)
|
||||
// terminal.loadAddon(new WebLinksAddon())
|
||||
setTerminal(terminal)
|
||||
fitAddon.fit()
|
||||
|
||||
return () => {
|
||||
if (terminal) {
|
||||
terminal.dispose()
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (terminal) {
|
||||
if (terminalRef.current) {
|
||||
terminal.open(terminalRef.current)
|
||||
setTerminal(terminal)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize ResizeObserver to handle resizing
|
||||
resizeObserverRef.current = new ResizeObserver(() => {
|
||||
fitAddon.fit()
|
||||
})
|
||||
|
||||
// Observe the terminal container for size changes
|
||||
if (terminalRef.current) {
|
||||
resizeObserverRef.current.observe(terminalRef.current)
|
||||
}
|
||||
|
||||
return () => {
|
||||
// Clean up the ResizeObserver
|
||||
if (resizeObserverRef.current) {
|
||||
resizeObserverRef.current.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [terminal])
|
||||
|
||||
useEffect(() => {
|
||||
if (!terminal) {
|
||||
return
|
||||
}
|
||||
|
||||
const socket = new WebSocket(endpoint)
|
||||
|
||||
socket.onopen = () => {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: 'init',
|
||||
payload: { namespace, podName },
|
||||
}),
|
||||
)
|
||||
console.log('WebSocket Client Connected')
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
socket.onmessage = event => {
|
||||
const data = JSON.parse(event.data)
|
||||
if (data.type === 'output') {
|
||||
if (data.payload.type === 'Buffer' && Array.isArray(data.payload.data)) {
|
||||
// Reconstruct bytes and decode to string
|
||||
// const bytes = new Uint8Array(data.payload)
|
||||
// const text = decoderRef.current.decode(bytes)
|
||||
|
||||
const text = Buffer.from(data.payload.data)
|
||||
console.log(text)
|
||||
terminal.write(text.toString('utf8'))
|
||||
} else {
|
||||
terminal.write(String(data.payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log('WebSocket Client Closed')
|
||||
}
|
||||
|
||||
socket.onerror = error => {
|
||||
console.error('WebSocket Error:', error)
|
||||
setError(error)
|
||||
}
|
||||
|
||||
terminal.onData(data => {
|
||||
if (data === '\u001bOP') {
|
||||
// const bufferToSend = Buffer.from('\u001b[11~', 'utf8')
|
||||
// socket.send(JSON.stringify({ type: 'input', payload: bufferToSend }))
|
||||
socket.send(JSON.stringify({ type: 'input', payload: '\u001b[11~' }))
|
||||
return
|
||||
}
|
||||
// const bufferToSend = Buffer.from(data, 'utf8')
|
||||
// socket.send(JSON.stringify({ type: 'input', payload: bufferToSend }))
|
||||
socket.send(JSON.stringify({ type: 'input', payload: data }))
|
||||
})
|
||||
|
||||
// terminal.onResize(size => {
|
||||
// socket.send(JSON.stringify({ type: 'resize', payload: { cols: size.cols, rows: size.rows } }))
|
||||
// })
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return () => {
|
||||
terminal.dispose()
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
}, [terminal, endpoint, namespace, podName])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Styled.CustomCard $isVisible={!isLoading && !error}>
|
||||
<Styled.FullWidthDiv>
|
||||
<div ref={terminalRef} />
|
||||
</Styled.FullWidthDiv>
|
||||
</Styled.CustomCard>
|
||||
{isLoading && !error && <Spin />}
|
||||
{error && <Result status="error" title="Error" subTitle={JSON.stringify(error)} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const TestWs: FC = () => {
|
||||
const cluster = useSelector((state: RootState) => state.cluster.cluster)
|
||||
const endpoint = `http://localhost:4002/api/clusters/${cluster}/openapi-bff/terminal/terminalPod/terminalPod`
|
||||
|
||||
return (
|
||||
<XTerminal
|
||||
endpoint={endpoint}
|
||||
namespace="incloud-web"
|
||||
podName="incloud-web-web-6cfc645577-thpmc"
|
||||
key={`${endpoint}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './TestWs'
|
||||
@@ -1,28 +0,0 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
const FullWidthDiv = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
type TCustomCardProps = {
|
||||
$isVisible?: boolean
|
||||
}
|
||||
|
||||
const CustomCard = styled.div<TCustomCardProps>`
|
||||
display: ${({ $isVisible }) => ($isVisible ? 'block' : 'none')};
|
||||
max-height: calc(100vh - 158px);
|
||||
margin: 24px;
|
||||
/* overflow-y: auto; */
|
||||
/* background: black; */
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
FullWidthDiv,
|
||||
CustomCard,
|
||||
}
|
||||
@@ -149,6 +149,12 @@ export default defineConfig({
|
||||
// })
|
||||
// },
|
||||
},
|
||||
'^/api/clusters/.*/openapi-bff-ws': {
|
||||
target: options?.BFF_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user