summaryrefslogtreecommitdiff
path: root/src/components/Logs.tsx
blob: 9baf3fa2e7ec29f5bb6f59df208796b768ca7ed3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import * as React from 'react';
import { ArrowDown, Pause, Play, Trash2 } from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';

import ContentHeader from '~/components/ContentHeader';
import LogSearch from '~/components/LogSearch';
import Select from '~/components/shared/Select';
import { useStoreActions } from '~/components/StateProvider';
import SvgYacd from '~/components/SvgYacd';
import useRemainingViewPortHeight from '~/hooks/useRemainingViewPortHeight';
import { useLogsPage } from '~/modules/logs/hooks';
import { LOG_LEVEL_OPTIONS } from '~/modules/config/utils';
import { LOG_TYPES, LOGS_HEIGHT_RATIO } from '~/modules/logs/utils';
import { updateConfigs } from '~/store/configs';
import { clearLogs } from '~/store/logs';
import { DispatchFn, Log } from '~/store/types';
import { ClashAPIConfig } from '~/types';

import s from './Logs.module.scss';
import { Fab, position as fabPosition } from './shared/Fab';

type LogLineProps = Partial<Log>;

function LogLine({ time, payload, type }: LogLineProps) {
  return (
    <div className={s.logLine}>
      <div className={s.logMeta}>
        <span className={s.logTime}>{time}</span>
        <span className={s.logType} data-type={type}>
          {LOG_TYPES[type]}
        </span>
      </div>
      <div className={s.logText}>{payload}</div>
    </div>
  );
}

type Props = {
  dispatch: DispatchFn;
  logLevel: string;
  apiConfig: ClashAPIConfig;
  logs: Log[];
  logStreamingPaused: boolean;
};

export default function Logs({ dispatch, logLevel, apiConfig, logs, logStreamingPaused }: Props) {
  const actions = useStoreActions();
  const { toggleIsRefreshPaused, scrollRef, isAtBottom, scrollToBottom, onScroll } = useLogsPage({
    dispatch,
    logLevel,
    apiConfig,
    logs,
    logStreamingPaused,
    updateAppConfig: actions.app.updateAppConfig,
  });
  const [refLogsContainer, containerHeight] = useRemainingViewPortHeight();
  const { t } = useTranslation();

  return (
    <div>
      <ContentHeader>
        <div className={s.headerControls}>
          <LogSearch className={s.searchWrapper} />
          <Select
            className={s.levelSelect}
            options={LOG_LEVEL_OPTIONS}
            selected={logLevel ? logLevel.toLowerCase() : 'info'}
            onChange={(e) => dispatch(updateConfigs(apiConfig, { 'log-level': e.target.value }))}
          />
          <button className={s.clearBtn} onClick={() => dispatch(clearLogs())} title={t('Clear')}>
            <Trash2 size={18} />
          </button>
        </div>
      </ContentHeader>
      <div ref={refLogsContainer} style={{ position: 'relative' }}>
        <div
          className={s.logsWrapper}
          style={{ height: containerHeight * LOGS_HEIGHT_RATIO }}
          ref={scrollRef}
          onScroll={onScroll}
        >
          {logs.length === 0 ? (
            <div className={s.logPlaceholder} style={{ height: '100%' }}>
              <div className={s.logPlaceholderIcon}>
                <SvgYacd width={200} height={200} />
              </div>
              <div>{t('no_logs')}</div>
            </div>
          ) : (
            logs.map((log, index) => <LogLine {...log} key={log.id || index} />)
          )}
        </div>

        {logs.length > 0 && !isAtBottom && (
          <button className={s.scrollToBottomBtn} onClick={scrollToBottom}>
            <ArrowDown size={16} />
          </button>
        )}

        <Fab
          icon={logStreamingPaused ? <Play size={16} /> : <Pause size={16} />}
          mainButtonStyles={logStreamingPaused ? { background: '#e74c3c' } : {}}
          style={fabPosition}
          text={logStreamingPaused ? t('Resume Refresh') : t('Pause Refresh')}
          onClick={toggleIsRefreshPaused}
        ></Fab>
      </div>
    </div>
  );
}