"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useXtermPlayer = exports.useIOLines = exports.useFetchIOEvents = void 0; var _xterm = require("xterm"); require("xterm/css/xterm.css"); var _react = require("react"); var _reactQuery = require("@tanstack/react-query"); var _public = require("@kbn/kibana-react-plugin/public"); var _xterm_search = require("./xterm_search"); var _hooks = require("../../hooks"); var _ansi_helpers = require("./ansi_helpers"); var _constants = require("../../../common/constants"); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ const useFetchIOEvents = (index, sessionEntityId, sessionStartTime) => { const { http } = (0, _public.useKibana)().services; const cachingKeys = (0, _react.useMemo)(() => [_constants.QUERY_KEY_IO_EVENTS, sessionEntityId], [sessionEntityId]); const query = (0, _reactQuery.useInfiniteQuery)(cachingKeys, async ({ pageParam = {} }) => { var _res$events$map, _res$events; const { cursor } = pageParam; const res = await http.get(_constants.IO_EVENTS_ROUTE, { version: _constants.CURRENT_API_VERSION, query: { index, sessionEntityId, sessionStartTime, cursor } }); const events = (_res$events$map = (_res$events = res.events) === null || _res$events === void 0 ? void 0 : _res$events.map(event => event._source)) !== null && _res$events$map !== void 0 ? _res$events$map : []; return { events, cursor, total: res.total }; }, { getNextPageParam: lastPage => { if (lastPage.events.length >= _constants.IO_EVENTS_PER_PAGE) { return { cursor: lastPage.events[lastPage.events.length - 1]['@timestamp'] }; } }, refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false }); return query; }; /** * flattens all pages of IO events into an array of lines, and builds up an array of process start markers */ exports.useFetchIOEvents = useFetchIOEvents; const useIOLines = pages => { const [cursor, setCursor] = (0, _react.useState)(0); const [processedLines, setProcessedLines] = (0, _react.useState)([]); const [processedMarkers, setProcessedMarkers] = (0, _react.useState)([]); const linesAndEntityIdMap = (0, _react.useMemo)(() => { if (!pages) { return { lines: processedLines, processStartMarkers: processedMarkers }; } const events = pages.reduce((previous, current) => previous.concat(current.events || []), []); const eventsToProcess = events.slice(cursor); const newMarkers = []; let newLines = []; eventsToProcess.forEach((event, index) => { var _process$io; const { process } = event; if ((process === null || process === void 0 ? void 0 : (_process$io = process.io) === null || _process$io === void 0 ? void 0 : _process$io.text) !== undefined && process.entity_id !== undefined) { var _newLines, _newLines$event, _newLines$event$proce, _processedLines, _processedLines$event; const previousProcessId = ((_newLines = newLines[newLines.length - 1]) === null || _newLines === void 0 ? void 0 : (_newLines$event = _newLines.event) === null || _newLines$event === void 0 ? void 0 : (_newLines$event$proce = _newLines$event.process) === null || _newLines$event$proce === void 0 ? void 0 : _newLines$event$proce.entity_id) || ((_processedLines = processedLines[processedLines.length - 1]) === null || _processedLines === void 0 ? void 0 : (_processedLines$event = _processedLines.event.process) === null || _processedLines$event === void 0 ? void 0 : _processedLines$event.entity_id); if (previousProcessId !== process.entity_id) { const processLineInfo = { line: processedLines.length + newLines.length, event }; newMarkers.push(processLineInfo); } if (process.io.max_bytes_per_process_exceeded) { const marker = newMarkers.find(item => { var _item$event$process; return ((_item$event$process = item.event.process) === null || _item$event$process === void 0 ? void 0 : _item$event$process.entity_id) === process.entity_id; }); if (marker) { marker.maxBytesExceeded = true; } } const splitLines = process.io.text.split(_constants.TTY_LINE_SPLITTER_REGEX); const combinedLines = [splitLines[0]]; // delimiters e.g \r\n or cursor movements are merged with their line text // we start on an odd number so that cursor movements happen at the start of each line // this is needed for the search to work accurately for (let i = 1; i < splitLines.length - 1; i = i + 2) { combinedLines.push(splitLines[i] + splitLines[i + 1]); } const data = combinedLines.map(line => { return { event, // pointer to the event so it's easy to look up other details for the line value: line }; }); newLines = newLines.concat(data); } }); const lines = processedLines.concat(newLines); const processStartMarkers = processedMarkers.concat(newMarkers); if (newLines.length > 0) { setProcessedLines(lines); } if (newMarkers.length > 0) { setProcessedMarkers(processStartMarkers); } const newCursor = cursor + eventsToProcess.length; if (newCursor > cursor) { setCursor(newCursor); } return { lines, processStartMarkers }; }, [cursor, pages, processedLines, processedMarkers]); return linesAndEntityIdMap; }; exports.useIOLines = useIOLines; const useXtermPlayer = ({ ref, isPlaying, setIsPlaying, lines, fontSize, hasNextPage, fetchNextPage, isFetching, policiesUrl }) => { var _lines$currentLine, _lines$currentLine$ev, _lines$currentLine2, _lines$currentLine2$e; const { euiTheme } = (0, _hooks.useEuiTheme)(); const { font, colors } = euiTheme; const [currentLine, setCurrentLine] = (0, _react.useState)(0); const [playSpeed] = (0, _react.useState)(_constants.DEFAULT_TTY_PLAYSPEED_MS); // potentially configurable const tty = lines === null || lines === void 0 ? void 0 : (_lines$currentLine = lines[currentLine]) === null || _lines$currentLine === void 0 ? void 0 : (_lines$currentLine$ev = _lines$currentLine.event.process) === null || _lines$currentLine$ev === void 0 ? void 0 : _lines$currentLine$ev.tty; const processName = lines === null || lines === void 0 ? void 0 : (_lines$currentLine2 = lines[currentLine]) === null || _lines$currentLine2 === void 0 ? void 0 : (_lines$currentLine2$e = _lines$currentLine2.event.process) === null || _lines$currentLine2$e === void 0 ? void 0 : _lines$currentLine2$e.name; const [terminal, searchAddon] = (0, _react.useMemo)(() => { const term = new _xterm.Terminal({ theme: { selectionBackground: colors.warning, selectionForeground: colors.ink, yellow: colors.warning }, fontFamily: font.familyCode, fontSize: _constants.DEFAULT_TTY_FONT_SIZE, scrollback: 0, convertEol: true, rows: _constants.DEFAULT_TTY_ROWS, cols: _constants.DEFAULT_TTY_COLS, allowProposedApi: true, allowTransparency: true }); const searchInstance = new _xterm_search.SearchAddon(); term.loadAddon(searchInstance); return [term, searchInstance]; }, [font, colors]); (0, _react.useEffect)(() => { if (ref.current && !terminal.element) { terminal.open(ref.current); } // even though we set scrollback: 0 above, xterm steals the wheel events and prevents the outer container from scrolling // this handler fixes that const onScroll = event => { var _event$target, _event$target$offsetP; if (event !== null && event !== void 0 && (_event$target = event.target) !== null && _event$target !== void 0 && (_event$target$offsetP = _event$target.offsetParent) !== null && _event$target$offsetP !== void 0 && _event$target$offsetP.classList.contains('xterm-screen')) { event.stopImmediatePropagation(); } }; window.addEventListener('wheel', onScroll, true); return () => { window.removeEventListener('wheel', onScroll, true); terminal.dispose(); }; }, [terminal, ref]); const render = (0, _react.useCallback)((lineNumber, clear) => { if (lines.length === 0) { return; } let linesToPrint; if (clear) { linesToPrint = lines.slice(Math.max(0, lineNumber - _constants.TTY_LINES_PRE_SEEK), lineNumber + 1); try { terminal.reset(); terminal.clear(); } catch (err) { // noop // there is some random race condition with the jump to feature that causes these calls to error out. } } else { linesToPrint = lines.slice(lineNumber, lineNumber + 1); } linesToPrint.forEach((line, index) => { var _line$event$process, _line$event$process$i; if ((line === null || line === void 0 ? void 0 : line.value) !== undefined) { terminal.write(line.value); } const nextLine = lines[lineNumber + index + 1]; const maxBytesExceeded = (_line$event$process = line.event.process) === null || _line$event$process === void 0 ? void 0 : (_line$event$process$i = _line$event$process.io) === null || _line$event$process$i === void 0 ? void 0 : _line$event$process$i.max_bytes_per_process_exceeded; // if next line is start of next event // and process has exceeded max bytes // render msg if (!clear && (!nextLine || nextLine.event !== line.event) && maxBytesExceeded) { const msg = (0, _ansi_helpers.renderTruncatedMsg)(tty, policiesUrl, processName); if (msg) { terminal.write(msg); } } }); }, [lines, policiesUrl, processName, terminal, tty]); (0, _react.useEffect)(() => { const fontChanged = terminal.options.fontSize !== fontSize; const ttyChanged = tty && (terminal.rows !== (tty === null || tty === void 0 ? void 0 : tty.rows) || terminal.cols !== (tty === null || tty === void 0 ? void 0 : tty.columns)); if (fontChanged) { terminal.options.fontSize = fontSize; } if (tty !== null && tty !== void 0 && tty.rows && tty !== null && tty !== void 0 && tty.columns && ttyChanged) { terminal.resize(tty.columns, tty.rows); } if (fontChanged || ttyChanged) { // clear and rerender render(currentLine, true); } if (!isFetching && hasNextPage && fetchNextPage && currentLine >= lines.length - 100) { fetchNextPage(); } }, [currentLine, fontSize, terminal, render, tty, hasNextPage, fetchNextPage, lines.length, isFetching]); (0, _react.useEffect)(() => { if (isPlaying) { const timer = setTimeout(() => { if (!hasNextPage && currentLine === lines.length - 1) { setIsPlaying(false); } else { const nextLine = Math.min(lines.length - 1, currentLine + 1); render(nextLine, false); setCurrentLine(nextLine); } }, playSpeed); return () => { clearTimeout(timer); }; } }, [lines, currentLine, isPlaying, playSpeed, render, hasNextPage, fetchNextPage, setIsPlaying]); const seekToLine = (0, _react.useCallback)(index => { setCurrentLine(index); render(index, true); }, [render]); const search = (0, _react.useCallback)((query, startCol) => { searchAddon.findNext(query, { caseSensitive: false, lastLineOnly: true, startCol }); }, [searchAddon]); return { terminal, currentLine, seekToLine, search }; }; exports.useXtermPlayer = useXtermPlayer;