export function LogViewer()

in src/components/LogViewer/index.tsx [57:201]


export function LogViewer(props: LogViewerProps) {
  const { logs, downloadName = 'log', xtermRef: outXtermRef, topActions = [] } = props;
  const [isFullScreen] = React.useState(false);
  const classes = useStyle({ isFullScreen });
  const xtermRef = React.useRef<XTerminal | null>(null);
  const fitAddonRef = React.useRef<any>(null);
  const searchAddonRef = React.useRef<any>(null);
  const [terminalContainerRef, setTerminalContainerRef] = React.useState<HTMLElement | null>(null);
  const [showSearch, setShowSearch] = React.useState(false);

  useHotkeys('ctrl+shift+f', () => {
    setShowSearch(true);
  });

  const XterminalReadonlyConfig: ITerminalOptions = {
    cursorStyle: 'bar',
    scrollback: 10000,
    rows: 30, // initial rows before fit
    fontFamily: 'IBM Plex Mono,monospace',
    fontSize: 12,
    lineHeight: 1.26,
  };

  function downloadLog() {
    // Cuts off the last 5 digits of the timestamp to remove the milliseconds
    const time = new Date().toISOString().replace(/:/g, '-').slice(0, -5);

    const element = document.createElement('a');
    const file = new Blob(logs, { type: 'text/plain' });
    element.href = URL.createObjectURL(file);
    element.download = `${downloadName}_${time}.txt`;
    // Required for FireFox
    document.body.appendChild(element);
    element.click();
  }

  React.useEffect(() => {
    if (!terminalContainerRef || !!xtermRef.current) {
      return;
    }

    fitAddonRef.current = new FitAddon();
    searchAddonRef.current = new SearchAddon();

    xtermRef.current = new XTerminal(XterminalReadonlyConfig);

    if (!!outXtermRef) {
      outXtermRef.current = xtermRef.current;
    }

    xtermRef.current.loadAddon(fitAddonRef.current);
    xtermRef.current.loadAddon(searchAddonRef.current);
    enableCopyPasteInXterm(xtermRef.current);

    xtermRef.current.open(terminalContainerRef!);

    fitAddonRef.current!.fit();

    xtermRef.current?.write(getJointLogs());

    const pageResizeHandler = () => {
      fitAddonRef.current!.fit();
      console.debug('resize');
    };
    window.addEventListener('resize', pageResizeHandler);

    return function cleanup() {
      window.removeEventListener('resize', pageResizeHandler);
      xtermRef.current?.dispose();
      searchAddonRef.current?.dispose();
      xtermRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [terminalContainerRef, xtermRef.current]);

  React.useEffect(() => {
    if (!xtermRef.current) {
      return;
    }

    // We're delegating to external xterm ref.
    if (!!outXtermRef) {
      return;
    }

    xtermRef.current?.clear();

    xtermRef.current?.write(getJointLogs());

    return function cleanup() {};
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [logs, xtermRef]);

  function getJointLogs() {
    return logs?.join('').replaceAll('\n', '\r\n');
  }

  return (
    <>
      <Grid container justifyContent="space-between" alignItems="center" wrap="nowrap">
        <Grid item container spacing={1}>
          {topActions.map((component, i) => (
            <Grid item key={i}>
              {component}
            </Grid>
          ))}
        </Grid>
        <Grid item xs>
          <ActionButton
            description={'Find'}
            onClick={() => setShowSearch((show) => !show)}
            icon="mdi:magnify"
          />
        </Grid>
        <Grid item xs>
          <ActionButton
            description={'Clear'}
            onClick={() => clearPodLogs(xtermRef)}
            icon="mdi:broom"
          />
        </Grid>
        <Grid item xs>
          <ActionButton
            description={'Download'}
            onClick={downloadLog}
            icon="mdi:file-download-outline"
          />
        </Grid>
      </Grid>
      <Box className={classes.logBox}>
        <div
          id="xterm-container"
          className={classes.terminal}
          ref={(ref) => setTerminalContainerRef(ref)}
          style={{ flex: 1, display: 'flex', flexDirection: 'column-reverse' }}
        />
        <SearchPopover
          open={showSearch}
          onClose={() => setShowSearch(false)}
          searchAddonRef={searchAddonRef}
        />
      </Box>
    </>
  );
}