-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(xterm): integrate xterm into connection for cluster or tenant
- Loading branch information
Showing
8 changed files
with
285 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { Terminal } from '@xterm/xterm' | ||
import { Button, Modal } from 'antd' | ||
import React from 'react' | ||
|
||
export interface ITerminal { | ||
terminalId: string | ||
onClose?: () => void | ||
onConnected?: () => void | ||
} | ||
|
||
function devLog(...args: any[]) { | ||
if (process.env.NODE_ENV === 'development') { | ||
console.log(...args) | ||
} | ||
} | ||
|
||
export const OBTerminal: React.FC<ITerminal> = (props) => { | ||
const { terminalId } = props | ||
const ref = React.useRef<HTMLDivElement>(null) | ||
const [ws, setWs] = React.useState<WebSocket | null>(null) | ||
|
||
React.useEffect(() => { | ||
if (ref.current) { | ||
const term = new Terminal({ | ||
cols: 160, | ||
rows: 60, | ||
}) | ||
term.open(ref.current) | ||
|
||
if (term.options.fontSize) { | ||
const containerWidth = ref.current.clientWidth | ||
|
||
const cols = Math.floor(containerWidth / 9.2) | ||
const rows = Math.floor(cols / 4) | ||
term.resize(cols, rows) | ||
const ws = new WebSocket(`ws://${location.host}/api/v1/terminal/${terminalId}?cols=${cols}&rows=${rows}`) | ||
term.write('Hello from \x1B[1;3;31mOceanBase\x1B[0m\r\n') | ||
|
||
ws.onopen = function () { | ||
devLog('Websocket connection open ...') | ||
// ws.send(JSON.stringify({ type: 'ping' })) | ||
term.onData(function (data) { | ||
ws.send(data) | ||
}) | ||
props.onConnected?.() | ||
setWs(ws) | ||
|
||
window.addEventListener('beforeunload', () => { | ||
if (ws) { | ||
ws.close() | ||
props.onClose?.() | ||
setWs(null) | ||
} | ||
}) | ||
} | ||
|
||
ws.onmessage = function (event) { | ||
term.write(event.data) | ||
} | ||
|
||
ws.onclose = function () { | ||
devLog('Connection closed.') | ||
term.write('\r\nConnection closed.\r\n') | ||
} | ||
|
||
ws.onerror = function (evt) { | ||
console.error('WebSocket error observed:', evt) | ||
} | ||
} | ||
} | ||
|
||
return () => { | ||
window.removeEventListener('beforeunload', () => { }) | ||
} | ||
}, []) | ||
|
||
return ( | ||
<> | ||
{ws && <div style={{ marginBottom: 12 }}> | ||
<Button danger type="primary" onClick={() => { | ||
Modal.confirm({ | ||
title: '断开连接', | ||
content: '确定要断开连接吗?', | ||
okType: 'danger', | ||
onOk: () => { | ||
ws.close() | ||
props.onClose?.() | ||
setWs(null) | ||
} | ||
}) | ||
}}>断开连接</Button> | ||
</div>} | ||
<div ref={ref} /> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React, { useState } from 'react' | ||
import { PageContainer } from '@ant-design/pro-components' | ||
import { intl } from '@/utils/intl' | ||
import { OBTerminal } from '@/components/Terminal/terminal' | ||
import { Button, Row, message } from 'antd' | ||
import { request } from '@umijs/max' | ||
import { useRequest } from 'ahooks' | ||
import { getNSName } from '../Overview/helper' | ||
import { getClusterDetailReq } from '@/services' | ||
import BasicInfo from '../Overview/BasicInfo' | ||
|
||
|
||
const ClusterConnection: React.FC = () => { | ||
const header = () => { | ||
return { | ||
title: intl.formatMessage({ | ||
id: 'dashboard.Cluster.Detail.Connection', | ||
defaultMessage: '集群连接', | ||
}) | ||
} | ||
} | ||
const [[ns, name]] = useState(getNSName()) | ||
|
||
const { data: clusterDetail } = useRequest(getClusterDetailReq, { | ||
defaultParams: [{ name, ns }], | ||
}) | ||
|
||
const { runAsync } = useRequest(async (): Promise<{ | ||
data: { terminalId: string } | ||
}> => { | ||
return request(`/api/v1/obclusters/namespace/${ns}/name/${name}/terminal`, { | ||
method: 'PUT' | ||
}) | ||
}, { | ||
manual: true | ||
}) | ||
|
||
const [terminalId, setTerminalId] = useState<string>() | ||
|
||
return ( | ||
<PageContainer header={header()}> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css" | ||
/> | ||
<Row gutter={[16, 16]}> | ||
{clusterDetail && ( | ||
<BasicInfo {...(clusterDetail.info as API.ClusterInfo)} /> | ||
)} | ||
<div style={{margin: 12, width: '100%'}}> | ||
{terminalId ? ( | ||
<OBTerminal terminalId={terminalId} onClose={() => { | ||
setTerminalId(undefined) | ||
message.info('连接已关闭') | ||
}} /> | ||
) : ( | ||
<Button onClick={async () => { | ||
if ((clusterDetail.info as API.ClusterInfo).status === 'failed') { | ||
message.error('集群未运行') | ||
return | ||
} | ||
const res = await runAsync() | ||
if (res?.data?.terminalId) { | ||
setTerminalId(res.data.terminalId) | ||
} | ||
}}>创建连接</Button> | ||
)} | ||
</div> | ||
</Row> | ||
</PageContainer> | ||
) | ||
} | ||
|
||
export default ClusterConnection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import React, { useEffect, useState } from 'react' | ||
import { PageContainer } from '@ant-design/pro-components' | ||
import { intl } from '@/utils/intl' | ||
import { OBTerminal } from '@/components/Terminal/terminal' | ||
import { Button, Row, message } from 'antd' | ||
import { request } from '@umijs/max' | ||
import { useRequest } from 'ahooks' | ||
import { getNSName } from '@/pages/Cluster/Detail/Overview/helper'; | ||
import BasicInfo from '../Overview/BasicInfo' | ||
import { getTenant } from '@/services/tenant' | ||
|
||
|
||
const TenantConnection: React.FC = () => { | ||
const header = () => { | ||
return { | ||
title: intl.formatMessage({ | ||
id: 'dashboard.Tenant.Detail.Connection', | ||
defaultMessage: '连接租户', | ||
}) | ||
} | ||
} | ||
|
||
const [[ns, name]] = useState(getNSName()); | ||
|
||
const { data: tenantDetailResponse, run: getTenantDetail } = useRequest(getTenant, { | ||
manual: true, | ||
}); | ||
|
||
const { runAsync } = useRequest(async (): Promise<{ | ||
data: { terminalId: string } | ||
}> => { | ||
return request(`/api/v1/obtenants/${ns}/${name}/terminal`, { | ||
method: 'PUT' | ||
}) | ||
}, { | ||
manual: true | ||
}) | ||
|
||
useEffect(() => { | ||
getTenantDetail({ ns, name }); | ||
}, []); | ||
|
||
const [terminalId, setTerminalId] = useState<string>() | ||
|
||
const tenantDetail = tenantDetailResponse?.data; | ||
|
||
return ( | ||
<PageContainer header={header()}> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css" | ||
/> | ||
<Row gutter={[16, 16]}> | ||
{tenantDetail && ( | ||
<BasicInfo info={tenantDetail.info} source={tenantDetail.source} /> | ||
)} | ||
<div style={{margin: 12, width: '100%'}}> | ||
{terminalId ? ( | ||
<OBTerminal terminalId={terminalId} onClose={() => { | ||
setTerminalId(undefined) | ||
message.info('连接已关闭') | ||
}} /> | ||
) : ( | ||
<Button onClick={async () => { | ||
if (!tenantDetail || tenantDetail.info.status === 'failed') { | ||
message.error('租户未正常运行') | ||
return | ||
} | ||
const res = await runAsync() | ||
if (res?.data?.terminalId) { | ||
setTerminalId(res.data.terminalId) | ||
} | ||
}}>创建连接</Button> | ||
)} | ||
</div> | ||
</Row> | ||
</PageContainer> | ||
) | ||
} | ||
|
||
export default TenantConnection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters