123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- <script setup lang="ts">
- import '@xterm/xterm/css/xterm.css'
- import { Terminal } from '@xterm/xterm'
- import { FitAddon } from '@xterm/addon-fit'
- import _ from 'lodash'
- import ws from '@/lib/websocket'
- import use2FAModal from '@/components/TwoFA/use2FAModal'
- import twoFA from '@/api/2fa'
- let term: Terminal | null
- let ping: NodeJS.Timeout
- const router = useRouter()
- const websocket = shallowRef()
- const lostConnection = ref(false)
- onMounted(() => {
- twoFA.secure_session_status()
- const otpModal = use2FAModal()
- otpModal.open().then(secureSessionId => {
- websocket.value = ws(`/api/pty?X-Secure-Session-ID=${secureSessionId}`, false)
- nextTick(() => {
- initTerm()
- websocket.value.onmessage = wsOnMessage
- websocket.value.onopen = wsOnOpen
- websocket.value.onerror = () => {
- lostConnection.value = true
- }
- websocket.value.onclose = () => {
- lostConnection.value = true
- }
- })
- }).catch(() => {
- if (window.history.length > 1)
- router.go(-1)
- else
- router.push('/')
- })
- })
- interface Message {
- Type: number
- Data: string | null | { Cols: number; Rows: number }
- }
- const fitAddon = new FitAddon()
- const fit = _.throttle(() => {
- fitAddon.fit()
- }, 50)
- function initTerm() {
- term = new Terminal({
- convertEol: true,
- fontSize: 14,
- cursorStyle: 'block',
- scrollback: 1000,
- theme: {
- background: '#000',
- },
- })
- term.loadAddon(fitAddon)
- term.open(document.getElementById('terminal')!)
- setTimeout(() => {
- fitAddon.fit()
- }, 60)
- window.addEventListener('resize', fit)
- term.focus()
- term.onData(key => {
- const order: Message = {
- Data: key,
- Type: 1,
- }
- sendMessage(order)
- })
- term.onBinary(data => {
- sendMessage({ Type: 1, Data: data })
- })
- term.onResize(data => {
- sendMessage({ Type: 2, Data: { Cols: data.cols, Rows: data.rows } })
- })
- }
- function sendMessage(data: Message) {
- websocket.value.send(JSON.stringify(data))
- }
- function wsOnMessage(msg: { data: string | Uint8Array }) {
- term!.write(msg.data)
- }
- function wsOnOpen() {
- ping = setInterval(() => {
- sendMessage({ Type: 3, Data: null })
- }, 30000)
- }
- onUnmounted(() => {
- window.removeEventListener('resize', fit)
- clearInterval(ping)
- term?.dispose()
- websocket.value?.close()
- })
- </script>
- <template>
- <ACard :title="$gettext('Terminal')">
- <AAlert
- v-if="lostConnection"
- class="mb-6"
- type="error"
- show-icon
- :message="$gettext('Connection lost, please refresh the page.')"
- />
- <div
- id="terminal"
- class="console"
- />
- </ACard>
- </template>
- <style lang="less" scoped>
- .console {
- min-height: calc(100vh - 300px);
- :deep(.terminal) {
- padding: 10px;
- }
- :deep(.xterm-viewport) {
- border-radius: 5px;
- }
- }
- </style>
|