import { useState, useCallback, useEffect } from 'react'; import { useCurrentStateAndParams } from '@uirouter/react'; import { Terminal as TerminalIcon } from 'lucide-react'; import { Terminal } from 'xterm'; import { useLocalStorage } from '@/react/hooks/useLocalStorage'; import { baseHref } from '@/portainer/helpers/pathHelper'; import { notifyError } from '@/portainer/services/notifications'; import { PageHeader } from '@@/PageHeader'; import { Widget, WidgetBody } from '@@/Widget'; import { Icon } from '@@/Icon'; import { Button } from '@@/buttons'; interface StringDictionary { [index: string]: string; } export function ConsoleView() { const { params: { endpointId: environmentId, container, name: appName, namespace, pod: podID, }, } = useCurrentStateAndParams(); const [jwtToken] = useLocalStorage('JWT', ''); const [command, setCommand] = useState('/bin/sh'); const [connectionStatus, setConnectionStatus] = useState('closed'); const [terminal, setTerminal] = useState(null as Terminal | null); const [socket, setSocket] = useState(null as WebSocket | null); const breadcrumbs = [ { label: 'Namespaces', link: 'kubernetes.resourcePools', }, { label: namespace, link: 'kubernetes.resourcePools.resourcePool', linkParams: { id: namespace }, }, { label: 'Applications', link: 'kubernetes.applications', }, { label: appName, link: 'kubernetes.applications.application', linkParams: { name: appName, namespace }, }, 'Pods', podID, 'Containers', container, 'Console', ]; const disconnectConsole = useCallback(() => { socket?.close(); terminal?.dispose(); setTerminal(null); setSocket(null); setConnectionStatus('closed'); }, [socket, terminal, setConnectionStatus]); useEffect(() => { if (socket) { socket.onopen = () => { const terminalContainer = document.getElementById('terminal-container'); if (terminalContainer) { terminal?.open(terminalContainer); terminal?.setOption('cursorBlink', true); terminal?.focus(); setConnectionStatus('open'); } }; socket.onmessage = (msg) => { terminal?.write(msg.data); }; socket.onerror = () => { disconnectConsole(); notifyError('Websocket connection error'); }; socket.onclose = () => { disconnectConsole(); }; } }, [disconnectConsole, setConnectionStatus, socket, terminal]); useEffect(() => { terminal?.on('data', (data) => { socket?.send(data); }); }, [terminal, socket]); return ( <>
Console
setCommand(e.target.value)} id="consoleCommand" // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus />
); function connectConsole() { const params: StringDictionary = { token: jwtToken, endpointId: environmentId, namespace, podName: podID, containerName: container, command, }; const queryParams = Object.keys(params) .map((k) => `${k}=${params[k]}`) .join('&'); let url = `${ window.location.origin }${baseHref()}api/websocket/pod?${queryParams}`; if (url.indexOf('https') > -1) { url = url.replace('https://', 'wss://'); } else { url = url.replace('http://', 'ws://'); } setConnectionStatus('connecting'); const term = new Terminal(); setTerminal(term); const socket = new WebSocket(url); setSocket(socket); } }