<commit_before>import {DataLoader, DropDownMenu, Tooltip} from 'argo-ui';
import * as classNames from 'classnames';
import * as React from 'react';
import {useState} from 'react';
import {Link} from 'react-router-dom';
import {Observable} from 'rxjs';

import * as models from '../../../shared/models';
import {services} from '../../../shared/services';

import {BASE_COLORS} from '../utils';

import './pod-logs-viewer.scss';

const maxLines = 100;
export interface PodLogsProps {
    namespace: string;
    applicationName: string;
    podName?: string;
    containerName: string;
    group?: string;
    kind?: string;
    name?: string;
    page: {number: number; untilTimes: string[]};
    setPage: (pageData: {number: number; untilTimes: string[]}) => void;
}

export const PodsLogsViewer = (props: PodLogsProps & {fullscreen?: boolean}) => {
    if (!props.containerName || props.containerName === '') {
        return <div>Pod does not have container with name {props.containerName}</div>;
    }

    let loader: DataLoader<models.LogEntry[], string>;
    const [copy, setCopy] = useState('');
    const [selectedLine, setSelectedLine] = useState(-1);
    const bottom = React.useRef<HTMLInputElement>(null);
    const top = React.useRef<HTMLInputElement>(null);
    const page = props.page;
    const setPage = props.setPage;
    const [viewPodNames, setViewPodNames] = useState(false);

    interface FilterData {
        literal: string;
        inverse: boolean;
    }
    const [filter, setFilter] = useState({inverse: false, literal: ''} as FilterData);

    const filterQuery = () => {
        return filter.literal && `${filter.inverse ? '!' : ''}${filter.literal}`;
    };
    const fullscreenURL =
        `/applications/${props.applicationName}/${props.namespace}/${props.containerName}/logs?` +
        `podName=${props.podName}&group=${props.group}&kind=${props.kind}&name=${props.name}`;
    return (
        <DataLoader load={() => services.viewPreferences.getPreferences()}>
            {prefs => (
                <React.Fragment>
                    <div className='pod-logs-viewer__settings'>
                        <button
                            className='argo-button argo-button--base'
                            style={{width: '100px'}}
                            onClick={async () => {
                                try {
                                    await navigator.clipboard.writeText(
                                        loader
                                            .getData()
                                            .map(item => item.content)
                                            .join('\n')
                                    );
                                    setCopy('success');
                                } catch (err) {
                                    setCopy('failure');
                                }
                                setTimeout(() => {
                                    setCopy('');
                                }, 750);
                            }}>
                            {copy === 'success' && (
                                <React.Fragment>
                                    COPIED <i className='fa fa-check' />
                                </React.Fragment>
                            )}
                            {copy === 'failure' && (
                                <React.Fragment>
                                    COPY FAILED <i className='fa fa-times' />
                                </React.Fragment>
                            )}
                            {copy === '' && (
                                <React.Fragment>
                                    COPY <i className='fa fa-clipboard' />
                                </React.Fragment>
                            )}
                        </button>
                        <button
                            className={classNames(`argo-button argo-button--base${prefs.appDetails.followLogs && page.number === 0 ? '' : '-o'}`, {
                                disabled: page.number > 0
                            })}
                            style={{width: '110px'}}
                            onClick={() => {
                                if (page.number > 0) {
                                    return;
                                }
                                const follow = !prefs.appDetails.followLogs;
                                services.viewPreferences.updatePreferences({...prefs, appDetails: {...prefs.appDetails, followLogs: follow}});
                                if (follow) {
                                    setPage({number: 0, untilTimes: []});
                                }
                                loader.reload();
                            }}>
                            FOLLOW {prefs.appDetails.followLogs && <i className='fa fa-check' />}
                        </button>
                        <button
                            className='argo-button argo-button--base-o'
                            onClick={() => {
                                const inverted = prefs.appDetails.darkMode;
                                services.viewPreferences.updatePreferences({...prefs, appDetails: {...prefs.appDetails, darkMode: !inverted}});
                            }}>
                            {prefs.appDetails.darkMode ? <i className='fa fa-sun' /> : <i className='fa fa-moon' />}
                        </button>
                        <button
                            className='argo-button argo-button--base'
                            style={{width: '100px'}}
                            onClick={async () => {
                                const downloadURL = services.applications.getDownloadLogsURL(
                                    props.applicationName,
                                    props.namespace,
                                    props.podName,
                                    {group: props.group, kind: props.kind, name: props.name},
                                    props.containerName
                                );
                                window.open(downloadURL, '_blank');
                            }}>
                            DOWNLOAD
                        </button>
                        {!props.fullscreen && (
                            <Link to={fullscreenURL} target='_blank' className='argo-button argo-button--base'>
                                <i className='fa fa-external-link-alt' />
                            </Link>
                        )}
                        <div className='pod-logs-viewer__filter'>
                            <button
                                className={`argo-button argo-button--base${filter.inverse ? '' : '-o'}`}
                                onClick={() => setFilter({...filter, inverse: !filter.inverse})}
                                style={{marginRight: '10px'}}>
                                !
                            </button>
                            <input
                                ref={input => {
                                    if (input) {
                                        Observable.fromEvent(input, 'keyup')
                                            .debounceTime(500)
                                            .subscribe(() => {
                                                if (loader) {
                                                    loader.reload();
                                                }
                                            });
                                    }
                                }}
                                type='text'
                                placeholder='Filter string'
                                className='argo-field'
                                value={filter.literal}
                                onChange={e => setFilter({...filter, literal: e.target.value})}
                                style={{padding: 0}}
                            />
                        </div>
                    </div>
                    <DataLoader
                        ref={l => (loader = l)}
                        loadingRenderer={() => (
                            <div className={`pod-logs-viewer ${prefs.appDetails.darkMode ? 'pod-logs-viewer--inverted' : ''}`}>
                                {logNavigators({}, prefs.appDetails.darkMode, null)}
                                <pre style={{height: '95%', textAlign: 'center'}}>{!prefs.appDetails.followLogs && 'Loading...'}</pre>
                            </div>
                        )}
                        input={props.containerName}
                        load={() => {
                            let logsSource = services.applications
                                .getContainerLogs(
                                    props.applicationName,
                                    props.namespace,
                                    props.podName,
                                    {group: props.group, kind: props.kind, name: props.name},
                                    props.containerName,
                                    maxLines * (page.number + 1),
                                    prefs.appDetails.followLogs && page.number === 0,
                                    page.untilTimes[page.untilTimes.length - 1],
                                    filterQuery()
                                )
                                // show only current page lines
                                .scan((lines, logEntry) => {
                                    // first equal true means retry attempt so we should clear accumulated log entries
                                    if (logEntry.first) {
                                        lines = [logEntry];
                                    } else {
                                        lines.push(logEntry);
                                    }
                                    if (lines.length > maxLines) {
                                        lines.splice(0, lines.length - maxLines);
                                    }
                                    return lines;
                                }, new Array<models.LogEntry>())
                                // accumulate log changes and render only once every 100ms to reduce CPU usage
                                .bufferTime(100)
                                .filter(batch => batch.length > 0)
                                .map(batch => batch[batch.length - 1]);
                            if (prefs.appDetails.followLogs) {
                                logsSource = logsSource.retryWhen(errors => errors.delay(500));
                            }
                            return logsSource;
                        }}>
                        {logs => {<commit_msg>fix(ui): Pod logs filter did not refresh on button click. Also add tooltip for clarification (#5858)<commit_after>import {DataLoader, DropDownMenu, Tooltip} from 'argo-ui';
import * as classNames from 'classnames';
import * as React from 'react';
import {useState} from 'react';
import {Link} from 'react-router-dom';

import * as models from '../../../shared/models';
import {services} from '../../../shared/services';

import {BASE_COLORS} from '../utils';

import './pod-logs-viewer.scss';

const maxLines = 100;
export interface PodLogsProps {
    namespace: string;
    applicationName: string;
    podName?: string;
    containerName: string;
    group?: string;
    kind?: string;
    name?: string;
    page: {number: number; untilTimes: string[]};
    setPage: (pageData: {number: number; untilTimes: string[]}) => void;
}

export const PodsLogsViewer = (props: PodLogsProps & {fullscreen?: boolean}) => {
    if (!props.containerName || props.containerName === '') {
        return <div>Pod does not have container with name {props.containerName}</div>;
    }

    let loader: DataLoader<models.LogEntry[], string>;
    const [copy, setCopy] = useState('');
    const [selectedLine, setSelectedLine] = useState(-1);
    const bottom = React.useRef<HTMLInputElement>(null);
    const top = React.useRef<HTMLInputElement>(null);
    const page = props.page;
    const setPage = props.setPage;
    const [viewPodNames, setViewPodNames] = useState(false);

    interface FilterData {
        literal: string;
        inverse: boolean;
    }

    const [filterText, setFilterText] = useState('');
    const [filter, setFilter] = useState({inverse: false, literal: ''} as FilterData);

    const formatFilter = (f: FilterData): string => {
        return f.literal && `${f.inverse ? '!' : ''}${f.literal}`;
    };

    const [filterQuery, setFilterQuery] = React.useState(formatFilter(filter));
    React.useEffect(() => {
        setFilterQuery(formatFilter(filter));
        if (loader) {
            loader.reload();
        }
    }, [filter]);

    React.useEffect(() => {
        const to = setTimeout(() => {
            setFilter({...filter, literal: filterText});
        }, 500);
        return () => clearTimeout(to);
    }, [filterText]);

    const fullscreenURL =
        `/applications/${props.applicationName}/${props.namespace}/${props.containerName}/logs?` +
        `podName=${props.podName}&group=${props.group}&kind=${props.kind}&name=${props.name}`;
    return (
        <DataLoader load={() => services.viewPreferences.getPreferences()}>
            {prefs => (
                <React.Fragment>
                    <div className='pod-logs-viewer__settings'>
                        <button
                            className='argo-button argo-button--base'
                            style={{width: '100px'}}
                            onClick={async () => {
                                try {
                                    await navigator.clipboard.writeText(
                                        loader
                                            .getData()
                                            .map(item => item.content)
                                            .join('\n')
                                    );
                                    setCopy('success');
                                } catch (err) {
                                    setCopy('failure');
                                }
                                setTimeout(() => {
                                    setCopy('');
                                }, 750);
                            }}>
                            {copy === 'success' && (
                                <React.Fragment>
                                    COPIED <i className='fa fa-check' />
                                </React.Fragment>
                            )}
                            {copy === 'failure' && (
                                <React.Fragment>
                                    COPY FAILED <i className='fa fa-times' />
                                </React.Fragment>
                            )}
                            {copy === '' && (
                                <React.Fragment>
                                    COPY <i className='fa fa-clipboard' />
                                </React.Fragment>
                            )}
                        </button>
                        <button
                            className={classNames(`argo-button argo-button--base${prefs.appDetails.followLogs && page.number === 0 ? '' : '-o'}`, {
                                disabled: page.number > 0
                            })}
                            style={{width: '110px'}}
                            onClick={() => {
                                if (page.number > 0) {
                                    return;
                                }
                                const follow = !prefs.appDetails.followLogs;
                                services.viewPreferences.updatePreferences({...prefs, appDetails: {...prefs.appDetails, followLogs: follow}});
                                if (follow) {
                                    setPage({number: 0, untilTimes: []});
                                }
                                loader.reload();
                            }}>
                            FOLLOW {prefs.appDetails.followLogs && <i className='fa fa-check' />}
                        </button>
                        <button
                            className='argo-button argo-button--base-o'
                            onClick={() => {
                                const inverted = prefs.appDetails.darkMode;
                                services.viewPreferences.updatePreferences({...prefs, appDetails: {...prefs.appDetails, darkMode: !inverted}});
                            }}>
                            {prefs.appDetails.darkMode ? <i className='fa fa-sun' /> : <i className='fa fa-moon' />}
                        </button>
                        <button
                            className='argo-button argo-button--base'
                            onClick={async () => {
                                const downloadURL = services.applications.getDownloadLogsURL(
                                    props.applicationName,
                                    props.namespace,
                                    props.podName,
                                    {group: props.group, kind: props.kind, name: props.name},
                                    props.containerName
                                );
                                window.open(downloadURL, '_blank');
                            }}>
                            DOWNLOAD
                        </button>
                        {!props.fullscreen && (
                            <Link to={fullscreenURL} target='_blank' className='argo-button argo-button--base'>
                                <i className='fa fa-external-link-alt' />
                            </Link>
                        )}
                        <div className='pod-logs-viewer__filter'>
                            <Tooltip content={`Show lines that ${!filter.inverse ? '' : 'do not'} match filter`}>
                                <button
                                    className={`argo-button argo-button--base${filter.inverse ? '' : '-o'}`}
                                    onClick={() => setFilter({...filter, inverse: !filter.inverse})}
                                    style={{marginRight: '10px'}}>
                                    !
                                </button>
                            </Tooltip>
                            <input
                                type='text'
                                placeholder='Filter string'
                                className='argo-field'
                                value={filterText}
                                onChange={e => setFilterText(e.target.value)}
                                style={{padding: 0}}
                            />
                        </div>
                    </div>
                    <DataLoader
                        ref={l => (loader = l)}
                        loadingRenderer={() => (
                            <div className={`pod-logs-viewer ${prefs.appDetails.darkMode ? 'pod-logs-viewer--inverted' : ''}`}>
                                {logNavigators({}, prefs.appDetails.darkMode, null)}
                                <pre style={{height: '95%', textAlign: 'center'}}>{!prefs.appDetails.followLogs && 'Loading...'}</pre>
                            </div>
                        )}
                        input={props.containerName}
                        load={() => {
                            let logsSource = services.applications
                                .getContainerLogs(
                                    props.applicationName,
                                    props.namespace,
                                    props.podName,
                                    {group: props.group, kind: props.kind, name: props.name},
                                    props.containerName,
                                    maxLines * (page.number + 1),
                                    prefs.appDetails.followLogs && page.number === 0,
                                    page.untilTimes[page.untilTimes.length - 1],
                                    filterQuery
                                )
                                // show only current page lines
                                .scan((lines, logEntry) => {
                                    // first equal true means retry attempt so we should clear accumulated log entries
                                    if (logEntry.first) {
                                        lines = [logEntry];
                                    } else {
                                        lines.push(logEntry);
                                    }
                                    if (lines.length > maxLines) {
                                        lines.splice(0, lines.length - maxLines);
                                    }
                                    return lines;
                                }, new Array<models.LogEntry>())
                                // accumulate log changes and render only once every 100ms to reduce CPU usage
                                .bufferTime(100)
                                .filter(batch => batch.length > 0)
                                .map(batch => batch[batch.length - 1]);
                            if (prefs.appDetails.followLogs) {
                                logsSource = logsSource.retryWhen(errors => errors.delay(500));
                            }
                            return logsSource;
                        }}>
                        {logs => {
{'fraction_non_alphanumeric': 0.10779179379753176, 'fraction_numerical': 0.0039619290495924225, 'mean_word_length': 0.8804589827025175, 'pattern_counts': {'":': 0, '<': 91, '<?xml version=': 0, '>': 147, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 129, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6016093', 'n_tokens_mistral': 4804, 'n_tokens_neox': 4572, 'n_words': 903}