mirror of
https://github.com/outbackdingo/openapi-ui.git
synced 2026-01-27 18:19:50 +00:00
pod terminal wip
This commit is contained in:
19
package-lock.json
generated
19
package-lock.json
generated
@@ -11,11 +11,13 @@
|
||||
"@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.62",
|
||||
"@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",
|
||||
"antd": "5.26.4",
|
||||
"axios": "1.4.0",
|
||||
"cross-env": "7.0.3",
|
||||
@@ -4436,6 +4438,21 @@
|
||||
"vite": "^4 || ^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-fit": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
|
||||
"integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
"@reduxjs/toolkit": "2.2.5",
|
||||
"@tanstack/react-query": "5.62.2",
|
||||
"@tanstack/react-query-devtools": "5.62.2",
|
||||
"@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",
|
||||
|
||||
@@ -12,6 +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'
|
||||
|
||||
type TFactoryProps = {
|
||||
setSidebarTags: (tags: string[]) => void
|
||||
@@ -59,6 +60,7 @@ 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}
|
||||
|
||||
169
src/components/organisms/Factory/TestWs/TestWs.tsx
Normal file
169
src/components/organisms/Factory/TestWs/TestWs.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
/* 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
src/components/organisms/Factory/TestWs/index.ts
Normal file
1
src/components/organisms/Factory/TestWs/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './TestWs'
|
||||
28
src/components/organisms/Factory/TestWs/styled.ts
Normal file
28
src/components/organisms/Factory/TestWs/styled.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user