document.addEventListener('alpine:init', () => { const hostOptions = [ { id: 'h1', name: 'api-prod-01', ip: '10.0.1.15' }, { id: 'h2', name: 'db-primary-01', ip: '10.0.2.5' }, { id: 'h6', name: 'monitor-01', ip: '10.0.0.5' }, { id: 'h7', name: 'worker-pool-01', ip: '10.0.4.50' }, ]; Alpine.data('terminalManager', () => ({ tabs: [], activeTab: 0, termInstances: {}, init() { this.addTab(); }, addTab() { const randomHost = hostOptions[Math.floor(Math.random() * hostOptions.length)]; const id = 'term-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6); this.tabs.push({ id, name: randomHost.name, host: randomHost.id, connected: false }); this.activeTab = this.tabs.length - 1; this.$nextTick(() => this.connectTerminal(id, randomHost.id, randomHost.name)); }, switchTab(i) { this.activeTab = i; }, closeTab(i) { const tab = this.tabs[i]; if (this.termInstances[tab.id]) { this.termInstances[tab.id].dispose(); delete this.termInstances[tab.id]; } this.tabs.splice(i, 1); if (this.tabs.length === 0) this.addTab(); else if (this.activeTab >= this.tabs.length) this.activeTab = this.tabs.length - 1; }, connectTerminal(id, hostId, hostName) { const container = document.getElementById('terminal-container-' + id); if (!container) return; const term = new Terminal({ cursorBlink: true, cursorStyle: 'bar', fontSize: 14, fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace", theme: { background: '#0d1117', foreground: '#c9d1d9', cursor: '#58a6ff', selection: '#264f78', black: '#484f58', red: '#ff7b72', green: '#3fb950', yellow: '#d29922', blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4', brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341', brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc', }, }); term.open(container); this.termInstances[id] = term; const tab = this.tabs.find(t => t.id === id); if (tab) tab.connected = false; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = protocol + '//' + window.location.host + '/terminal/ws/' + hostId; let ws; try { ws = new WebSocket(wsUrl); } catch (e) { term.writeln('\x1b[1;31mWebSocket connection failed\x1b[0m'); return; } ws.onopen = () => { if (tab) tab.connected = true; term.focus(); }; ws.onmessage = (e) => { if (e.data instanceof Blob) { const reader = new FileReader(); reader.onload = () => term.write(reader.result); reader.readAsText(e.data); } else { term.write(e.data); } }; ws.onerror = () => { term.writeln('\x1b[1;31m\x1b[7m CONNECTION ERROR \x1b[0m'); }; ws.onclose = () => { if (tab) tab.connected = false; term.writeln('\r\n\x1b[2mConnection closed.\x1b[0m'); }; term.onData((data) => { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(data); } }); term.attachCustomKeyEventHandler((e) => { if (e.type === 'keydown' && e.ctrlKey && e.key === 'c') { return true; } return true; }); }, })); });