summary history files

frontend/src/components/Terminal.vue
<script>
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links';
import '@xterm/xterm/css/xterm.css';

export default {
  name: 'Terminal',

  props: {
    selectedPod: {
      type: Object,
      required: true
    },
    selectedNamespace: {
      type: Object,
      required: true
    },
    selectedCluster: {
      type: Object,
      required: true
    }
  },

  data() {
    return {
      terminal: null,
      fitAddon: null,
      socket: null,
      isConnected: false,
      reconnectAttempts: 0,
      maxReconnectAttempts: 3
    }
  },

  mounted() {
    this.initializeTerminal();
    window.addEventListener('resize', this.onResize);
  },

  beforeUnmount() {
    this.cleanup();
    window.removeEventListener('resize', this.onResize);
  },

  methods: {
    initializeTerminal() {
      // Initialize xterm.js terminal
      this.terminal = new Terminal({
        cursorBlink: true,
        fontSize: 14,
        fontFamily: 'Menlo, Monaco, "Courier New", monospace',
        theme: {
          background: '#1e1e1e',
          foreground: '#ffffff',
          cursor: '#ffffff',
          selection: 'rgba(255, 255, 255, 0.3)',
          black: '#000000',
          blue: '#2472c8',
          brightBlue: '#3b8eea',
          brightCyan: '#29b8db',
          brightGreen: '#23d18b',
          brightMagenta: '#d670d6',
          brightRed: '#f14c4c',
          brightWhite: '#e5e5e5',
          brightYellow: '#f5f543',
          cyan: '#11a8cd',
          green: '#0dbc79',
          magenta: '#bc3fbc',
          red: '#cd3131',
          white: '#e5e5e5',
          yellow: '#e5e510'
        }
      });

      // Add the fit addon
      this.fitAddon = new FitAddon();
      this.terminal.loadAddon(this.fitAddon);

      // Add web links addon
      this.terminal.loadAddon(new WebLinksAddon());

      // Open terminal in the container
      this.terminal.open(this.$refs.terminal);

      // Initial fit
      setTimeout(() => {
        this.fitAddon.fit();
      }, 100);

      // Connect to WebSocket
      this.connectWebSocket();
    },

    connectWebSocket() {
      // If already connecting, don't try again
      if (this.isReconnecting) {
        return;
      }

      this.isReconnecting = true;

      // Close existing socket if any
      if (this.socket) {
        this.socket.close();
        this.socket = null;
      }

      const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
      const wsUrl = `${wsProtocol}//${window.location.hostname}:8081/api/pods/exec?` + 
        `cluster=${encodeURIComponent(this.selectedCluster.server)}&` +
        `namespace=${encodeURIComponent(this.selectedNamespace.name)}&` +
        `pod=${encodeURIComponent(this.selectedPod.metadata.name)}`;

      try {
        this.socket = new WebSocket(wsUrl);

        this.socket.onopen = () => {
          this.isConnected = true;
          this.reconnectAttempts = 0;
          this.isReconnecting = false;
          this.terminal.writeln('Connected to pod shell...');
          this.terminal.focus();
        };

        this.socket.onmessage = (event) => {
          if (this.terminal) {
            this.terminal.write(event.data);
          }
        };

        this.socket.onclose = () => {
          this.isConnected = false;
          this.isReconnecting = false;
          if (this.terminal) {
            this.terminal.writeln('\r\nConnection closed');
          }
        };

        this.socket.onerror = (error) => {
          console.error('WebSocket error:', error);
          this.isReconnecting = false;
          if (this.terminal) {
            this.terminal.writeln('\r\nError: Failed to connect to pod shell');
          }
        };

        // Handle terminal input
        if (!this.terminal._initialized) {
          this.terminal.onData(data => {
            if (this.isConnected && this.socket?.readyState === WebSocket.OPEN) {
              this.socket.send(data);
            }
          });
          this.terminal._initialized = true;
        }

      } catch (error) {
        console.error('Failed to connect to WebSocket:', error);
        this.isReconnecting = false;
        if (this.terminal) {
          this.terminal.writeln('Failed to connect to pod shell');
        }
      }
    },

    attemptReconnect() {
      if (this.reconnectAttempts < this.maxReconnectAttempts) {
        this.reconnectAttempts++;
        this.terminal.writeln(`\r\nAttempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
        setTimeout(() => {
          this.connectWebSocket();
        }, 2000);
      }
    },

    onResize() {
      if (this.fitAddon) {
        this.fitAddon.fit();
      }
    },

    
    cleanup() {
      if (this.socket) {
        this.socket.close();
        this.socket = null;
      }

      if (this.terminal) {
        if (this.fitAddon) {
          try {
            this.fitAddon.dispose();
          } catch (e) {
            console.log('FitAddon cleanup:', e);
          }
          this.fitAddon = null;
        }

        try {
          this.terminal.dispose();
        } catch (e) {
          console.log('Terminal cleanup:', e);
        }
        this.terminal = null;
      }

      this.isConnected = false;
      this.isReconnecting = false;
      this.reconnectAttempts = 0;
    },

    closeTerminal() {
      this.cleanup();
      this.$emit('close');
    }

  }
}
</script>

<template>
  <div class="terminal-container">
    <div class="terminal-header">
      <span>Terminal: {{ selectedPod?.metadata?.name }}</span>
      <button @click="closeTerminal" class="close-btn">×</button>
    </div>
    <div id="terminal" ref="terminal" class="terminal-content"></div>
  </div>
</template>


<style scoped>
.terminal-container {
  position: fixed;
  bottom: 20px;
  right: 20px;
  width: 800px;
  height: 500px;
  background: #1e1e1e;
  border: 1px solid #333;
  border-radius: 6px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.terminal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  background: #333;
  color: white;
  font-size: 14px;
  user-select: none;
}

.close-btn {
  background: none;
  border: none;
  color: white;
  font-size: 20px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}

.close-btn:hover {
  color: #ff5252;
}

.terminal-content {
  flex: 1;
  padding: 4px;
  background-color: #1e1e1e;
  text-align: left; /* Add this */
}

:deep(.xterm) {
  padding: 8px;
  height: 100%;
  text-align: left; /* Add this */
}

:deep(.xterm-viewport) {
  background-color: #1e1e1e !important;
}

/* Add these new styles */
:deep(.xterm-screen) {
  text-align: left !important;
}

:deep(.xterm-rows) {
  text-align: left !important;
}
</style>