import { Meteor } from 'meteor/meteor'; import React, { useEffect, useRef, useState, useCallback } from 'react'; import { Box, Icon, Scrollable } from '@rocket.chat/fuselage'; import Page from '../../components/basic/Page'; import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; import { useEndpoint } from '../../contexts/ServerContext'; import { useTranslation } from '../../contexts/TranslationContext'; const foregroundColors = { 30: 'gray', 31: 'red', 32: 'lime', 33: 'yellow', 34: '#6B98FF', 35: '#FF00FF', 36: 'cyan', 37: 'white', }; const ansispan = (str) => { str = str .replace(/\s/g, ' ') .replace(/(\\n|\n)/g, '
') .replace(/>/g, '>') .replace(/$1') .replace(/\033\[1m/g, '') .replace(/\033\[22m/g, '') .replace(/\033\[3m/g, '') .replace(/\033\[23m/g, '') .replace(/\033\[m/g, '') .replace(/\033\[0m/g, '') .replace(/\033\[39m/g, ''); return Object.entries(foregroundColors).reduce((str, [ansiCode, color]) => { const span = ``; return ( str .replace(new RegExp(`\\033\\[${ ansiCode }m`, 'g'), span) .replace(new RegExp(`\\033\\[0;${ ansiCode }m`, 'g'), span) ); }, str); }; function ViewLogs() { const [lines, setLines] = useState([]); window.setLines = setLines; const dispatchToastMessage = useToastMessageDispatch(); const getStdoutQueue = useEndpoint('GET', 'stdout.queue'); useEffect(() => { const fetchLines = async () => { try { const { queue } = await getStdoutQueue(); setLines(queue); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } }; fetchLines(); }, []); useEffect(() => { const stdoutStreamer = new Meteor.Streamer('stdout'); const handleStdout = (line) => { setLines((lines) => [...lines, line]); }; stdoutStreamer.on('stdout', handleStdout); return () => { stdoutStreamer.removeListener('stdout'); }; }, []); const t = useTranslation(); const wrapperRef = useRef(); const atBottomRef = useRef(); const [newLogsVisible, setNewLogsVisible] = useState(false); const isAtBottom = useCallback((scrollThreshold) => { const wrapper = wrapperRef.current; if (scrollThreshold == null) { scrollThreshold = 0; } if (wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight) { setNewLogsVisible(false); return true; } return false; }, []); const sendToBottom = useCallback(() => { const wrapper = wrapperRef.current; wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight; setNewLogsVisible(false); }, []); const checkIfScrollIsAtBottom = useCallback(() => { atBottomRef.current = isAtBottom(100); }, [isAtBottom]); const sendToBottomIfNecessary = useCallback(() => { if (atBottomRef.current === true && isAtBottom() !== true) { sendToBottom(); } else if (atBottomRef.current === false) { setNewLogsVisible(true); } }, [isAtBottom, sendToBottom]); useEffect(() => { if (window.MutationObserver) { const observer = new MutationObserver((mutations) => { mutations.forEach(() => { sendToBottomIfNecessary(); }); }); observer.observe(wrapperRef.current, { childList: true }); return () => { observer.disconnect(); }; } const handleSubtreeModified = () => { sendToBottomIfNecessary(); }; wrapperRef.current.addEventListener('DOMSubtreeModified', handleSubtreeModified); return () => { wrapperRef.current.removeEventListener('DOMSubtreeModified', handleSubtreeModified); }; }, [sendToBottomIfNecessary]); useEffect(() => { const handleWindowResize = () => { setTimeout(() => { sendToBottomIfNecessary(); }, 100); }; window.addEventListener('resize', handleWindowResize); return () => { window.removeEventListener('resize', handleWindowResize); }; }, [sendToBottomIfNecessary]); const handleWheel = useCallback(() => { atBottomRef.current = false; setTimeout(() => { checkIfScrollIsAtBottom(); }, 100); }, [checkIfScrollIsAtBottom]); const handleTouchStart = () => { atBottomRef.current = false; }; const handleTouchEnd = useCallback(() => { setTimeout(() => { checkIfScrollIsAtBottom(); }, 100); }, [checkIfScrollIsAtBottom]); const handleScroll = useCallback(() => { atBottomRef.current = false; setTimeout(() => { checkIfScrollIsAtBottom(); }, 100); }, [checkIfScrollIsAtBottom]); const handleClick = useCallback(() => { atBottomRef.current = true; sendToBottomIfNecessary(); }, [sendToBottomIfNecessary]); return {lines.sort((a, b) => a.ts - b.ts).map(({ string }, i) => )} {t('New_logs')} ; } export default ViewLogs;