TunnelTerminal.vue 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. <template>
  2. <div class="terminal-page">
  3. <div v-if="loading" class="loading">Loading terminal...</div>
  4. <div v-else-if="error" class="error">{{ error }}</div>
  5. <iframe
  6. v-else-if="ttydUrl"
  7. :src="ttydUrl"
  8. class="terminal-iframe"
  9. @load="onIframeLoad"
  10. />
  11. </div>
  12. </template>
  13. <script setup>
  14. import { ref, onMounted, onBeforeUnmount } from 'vue'
  15. import { useRoute } from 'vue-router'
  16. import tunnelsApi from '@/api/tunnels'
  17. const route = useRoute()
  18. const sessionUuid = route.params.uuid
  19. const ttydUrl = ref(null)
  20. const loading = ref(true)
  21. const error = ref(null)
  22. let heartbeatInterval = null
  23. async function loadTerminal() {
  24. loading.value = true
  25. error.value = null
  26. try {
  27. const status = await tunnelsApi.getSessionStatus(sessionUuid)
  28. if (status.status === 'ready' && status.ttyd_port) {
  29. // Backend проксирует WebSocket на localhost ttyd
  30. ttydUrl.value = `http://192.168.5.4:8000/api/v1/superadmin/tunnels/sessions/${sessionUuid}/terminal`
  31. loading.value = false
  32. // Start heartbeat
  33. startHeartbeat()
  34. } else if (status.status === 'failed') {
  35. error.value = 'Tunnel connection failed'
  36. loading.value = false
  37. } else {
  38. error.value = 'Terminal not ready yet'
  39. loading.value = false
  40. }
  41. } catch (err) {
  42. console.error('Failed to load terminal:', err)
  43. error.value = err.response?.data?.detail || 'Failed to load terminal'
  44. loading.value = false
  45. }
  46. }
  47. function startHeartbeat() {
  48. // Send heartbeat every 30 seconds
  49. heartbeatInterval = setInterval(async () => {
  50. try {
  51. await tunnelsApi.sendHeartbeat(sessionUuid)
  52. console.log('Heartbeat sent')
  53. } catch (err) {
  54. console.error('Heartbeat failed:', err)
  55. }
  56. }, 30000)
  57. }
  58. function onIframeLoad() {
  59. console.log('Terminal loaded')
  60. }
  61. onMounted(() => {
  62. loadTerminal()
  63. })
  64. onBeforeUnmount(() => {
  65. if (heartbeatInterval) {
  66. clearInterval(heartbeatInterval)
  67. }
  68. })
  69. </script>
  70. <style scoped>
  71. .terminal-page {
  72. position: fixed;
  73. top: 0;
  74. left: 0;
  75. right: 0;
  76. bottom: 0;
  77. background: #1e1e1e;
  78. display: flex;
  79. align-items: center;
  80. justify-content: center;
  81. }
  82. .loading, .error {
  83. color: #ffffff;
  84. font-size: 18px;
  85. padding: 40px;
  86. text-align: center;
  87. }
  88. .error {
  89. color: #ff6b6b;
  90. }
  91. .terminal-iframe {
  92. width: 100%;
  93. height: 100%;
  94. border: none;
  95. }
  96. </style>