Make log stream pause global state
This commit is contained in:
parent
ab99bd3773
commit
d391e9efbf
9 changed files with 67 additions and 98 deletions
|
@ -77,15 +77,7 @@ function formatConnectionDataItem(
|
|||
now: number
|
||||
): FormattedConn {
|
||||
const { id, metadata, upload, download, start, chains, rule } = i;
|
||||
const {
|
||||
host,
|
||||
destinationPort,
|
||||
destinationIP,
|
||||
network,
|
||||
type,
|
||||
sourceIP,
|
||||
sourcePort,
|
||||
} = metadata;
|
||||
const { host, destinationPort, destinationIP, network, type, sourceIP, sourcePort } = metadata;
|
||||
// host could be an empty string if it's direct IP connection
|
||||
let host2 = host;
|
||||
if (host2 === '') host2 = destinationIP;
|
||||
|
@ -130,10 +122,7 @@ function Conn({ apiConfig }) {
|
|||
const filteredClosedConns = filterConns(closedConns, filterKeyword);
|
||||
const [isCloseAllModalOpen, setIsCloseAllModalOpen] = useState(false);
|
||||
const openCloseAllModal = useCallback(() => setIsCloseAllModalOpen(true), []);
|
||||
const closeCloseAllModal = useCallback(
|
||||
() => setIsCloseAllModalOpen(false),
|
||||
[]
|
||||
);
|
||||
const closeCloseAllModal = useCallback(() => setIsCloseAllModalOpen(false), []);
|
||||
const [isRefreshPaused, setIsRefreshPaused] = useState(false);
|
||||
const toggleIsRefreshPaused = useCallback(() => {
|
||||
setIsRefreshPaused((x) => !x);
|
||||
|
@ -161,11 +150,7 @@ function Conn({ apiConfig }) {
|
|||
});
|
||||
// if previous connections and current connections are both empty
|
||||
// arrays, we wont update state to avaoid rerender
|
||||
if (
|
||||
x &&
|
||||
(x.length !== 0 || prevConnsRef.current.length !== 0) &&
|
||||
!isRefreshPaused
|
||||
) {
|
||||
if (x && (x.length !== 0 || prevConnsRef.current.length !== 0) && !isRefreshPaused) {
|
||||
prevConnsRef.current = x;
|
||||
setConns(x);
|
||||
} else {
|
||||
|
@ -218,10 +203,7 @@ function Conn({ apiConfig }) {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={refContainer}
|
||||
style={{ padding: 30, paddingBottom, paddingTop: 0 }}
|
||||
>
|
||||
<div ref={refContainer} style={{ padding: 30, paddingBottom, paddingTop: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
height: containerHeight - paddingBottom,
|
||||
|
@ -231,20 +213,13 @@ function Conn({ apiConfig }) {
|
|||
<TabPanel>
|
||||
<>{renderTableOrPlaceholder(filteredConns)}</>
|
||||
<Fab
|
||||
icon={
|
||||
isRefreshPaused ? <Play size={16} /> : <Pause size={16} />
|
||||
}
|
||||
mainButtonStyles={
|
||||
isRefreshPaused ? { background: '#e74c3c' } : {}
|
||||
}
|
||||
icon={isRefreshPaused ? <Play size={16} /> : <Pause size={16} />}
|
||||
mainButtonStyles={isRefreshPaused ? { background: '#e74c3c' } : {}}
|
||||
style={fabPosition}
|
||||
text={isRefreshPaused ? 'Resume Refresh' : 'Pause Refresh'}
|
||||
text={isRefreshPaused ? t('Resume Refresh') : t('Pause Refresh')}
|
||||
onClick={toggleIsRefreshPaused}
|
||||
>
|
||||
<Action
|
||||
text="Close All Connections"
|
||||
onClick={openCloseAllModal}
|
||||
>
|
||||
<Action text="Close All Connections" onClick={openCloseAllModal}>
|
||||
<IconClose size={10} />
|
||||
</Action>
|
||||
</Fab>
|
||||
|
|
|
@ -41,17 +41,14 @@
|
|||
color: var(--color-text);
|
||||
|
||||
:global {
|
||||
li {
|
||||
background: var(--color-background);
|
||||
}
|
||||
li.even {
|
||||
background: var(--color-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.log {
|
||||
.log {
|
||||
padding: 10px 40px;
|
||||
background: var(--color-background);
|
||||
}
|
||||
.log.even {
|
||||
background: var(--color-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************/
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import cx from 'clsx';
|
||||
import * as React from 'react';
|
||||
import { Pause, Play } from 'react-feather';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { areEqual, FixedSizeList as List, ListChildComponentProps } from 'react-window';
|
||||
import { fetchLogs, stop as stopLogs, reconnect as reconnectLogs } from 'src/api/logs';
|
||||
import { fetchLogs, reconnect as reconnectLogs,stop as stopLogs } from 'src/api/logs';
|
||||
import ContentHeader from 'src/components/ContentHeader';
|
||||
import LogSearch from 'src/components/LogSearch';
|
||||
import { connect } from 'src/components/StateProvider';
|
||||
import { connect, useStoreActions } from 'src/components/StateProvider';
|
||||
import SvgYacd from 'src/components/SvgYacd';
|
||||
import useRemainingViewPortHeight from 'src/hooks/useRemainingViewPortHeight';
|
||||
import { getClashAPIConfig } from 'src/store/app';
|
||||
import { getClashAPIConfig, getLogStreamingPaused } from 'src/store/app';
|
||||
import { getLogLevel } from 'src/store/configs';
|
||||
import { appendLog, getLogsForDisplay } from 'src/store/logs';
|
||||
import { Log, State } from 'src/store/types';
|
||||
import { Pause, Play } from 'react-feather';
|
||||
import { Fab, position as fabPosition } from './shared/Fab';
|
||||
|
||||
import s from './Logs.module.scss';
|
||||
import { Fab, position as fabPosition } from './shared/Fab';
|
||||
|
||||
const { useState, useCallback, memo, useEffect } = React;
|
||||
const { useCallback, memo, useEffect } = React;
|
||||
|
||||
const paddingBottom = 30;
|
||||
const colors = {
|
||||
|
@ -30,7 +30,7 @@ const colors = {
|
|||
type LogLineProps = Partial<Log>;
|
||||
|
||||
function LogLine({ time, even, payload, type }: LogLineProps) {
|
||||
const className = cx({ even }, s.log);
|
||||
const className = cx({ even }, 'log');
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={s.logMeta}>
|
||||
|
@ -58,12 +58,14 @@ const Row = memo(({ index, style, data }: ListChildComponentProps<LogLineProps>)
|
|||
);
|
||||
}, areEqual);
|
||||
|
||||
function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
||||
const [isRefreshPaused, setIsRefreshPaused] = useState(false);
|
||||
function Logs({ dispatch, logLevel, apiConfig, logs, logStreamingPaused }) {
|
||||
const actions = useStoreActions();
|
||||
const toggleIsRefreshPaused = useCallback(() => {
|
||||
isRefreshPaused ? reconnectLogs({ ...apiConfig, logLevel }) : stopLogs();
|
||||
setIsRefreshPaused((x) => !x);
|
||||
}, [isRefreshPaused, apiConfig, logLevel]);
|
||||
logStreamingPaused ? reconnectLogs({ ...apiConfig, logLevel }) : stopLogs();
|
||||
// being lazy here
|
||||
// ideally we should check the result of previous operation before updating this
|
||||
actions.app.updateAppConfig('logStreamingPaused', !logStreamingPaused);
|
||||
}, [apiConfig, logLevel, logStreamingPaused, actions.app]);
|
||||
const appendLogInternal = useCallback((log) => dispatch(appendLog(log)), [dispatch]);
|
||||
useEffect(() => {
|
||||
fetchLogs({ ...apiConfig, logLevel }, appendLogInternal);
|
||||
|
@ -97,10 +99,10 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
|||
</List>
|
||||
|
||||
<Fab
|
||||
icon={isRefreshPaused ? <Play size={16} /> : <Pause size={16} />}
|
||||
mainButtonStyles={isRefreshPaused ? { background: '#e74c3c' } : {}}
|
||||
icon={logStreamingPaused ? <Play size={16} /> : <Pause size={16} />}
|
||||
mainButtonStyles={logStreamingPaused ? { background: '#e74c3c' } : {}}
|
||||
style={fabPosition}
|
||||
text={isRefreshPaused ? 'Resume Refresh' : 'Pause Refresh'}
|
||||
text={logStreamingPaused ? t('Resume Refresh') : t('Pause Refresh')}
|
||||
onClick={toggleIsRefreshPaused}
|
||||
></Fab>
|
||||
</div>
|
||||
|
@ -114,6 +116,7 @@ const mapState = (s: State) => ({
|
|||
logs: getLogsForDisplay(s),
|
||||
logLevel: getLogLevel(s),
|
||||
apiConfig: getClashAPIConfig(s),
|
||||
logStreamingPaused: getLogStreamingPaused(s),
|
||||
});
|
||||
|
||||
export default connect(mapState)(Logs);
|
||||
|
|
|
@ -36,8 +36,7 @@ h2.sectionNameType {
|
|||
*
|
||||
*/
|
||||
|
||||
:global {
|
||||
body.light {
|
||||
:root[data-theme='light'] {
|
||||
/*
|
||||
* --loading-dot-{dot-index}-{dot-keyframe-phase}
|
||||
*/
|
||||
|
@ -50,8 +49,8 @@ h2.sectionNameType {
|
|||
--loading-dot-3-1: rgba(0, 0, 0, 0.5);
|
||||
--loading-dot-3-2: rgba(0, 0, 0, 0.3);
|
||||
--loading-dot-3-3: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
body.dark {
|
||||
}
|
||||
:root[data-theme='dark'] {
|
||||
--loading-dot-1-1: rgba(255, 255, 255, 0.5);
|
||||
--loading-dot-1-2: rgba(255, 255, 255, 0.1);
|
||||
--loading-dot-1-3: rgba(255, 255, 255, 0.3);
|
||||
|
@ -61,7 +60,6 @@ h2.sectionNameType {
|
|||
--loading-dot-3-1: rgba(255, 255, 255, 0.1);
|
||||
--loading-dot-3-2: rgba(255, 255, 255, 0.3);
|
||||
--loading-dot-3-3: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.loadingDot,
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
list-style: none;
|
||||
}
|
||||
.rtf.open .rtf--mb {
|
||||
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2),
|
||||
0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
0px 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.rtf.open .rtf--mb > ul {
|
||||
|
|
|
@ -10,6 +10,8 @@ export const data = {
|
|||
'Upload Total': 'Upload Total',
|
||||
'Download Total': 'Download Total',
|
||||
'Active Connections': 'Active Connections',
|
||||
'Pause Refresh': 'Pause Refresh',
|
||||
'Resume Refresh': 'Resume Refresh',
|
||||
Up: 'Up',
|
||||
Down: 'Down',
|
||||
'Test Latency': 'Test Latency',
|
||||
|
|
|
@ -10,6 +10,8 @@ export const data = {
|
|||
'Upload Total': '上传总量',
|
||||
'Download Total': '下载总量',
|
||||
'Active Connections': '活动连接',
|
||||
'Pause Refresh': '暂停刷新',
|
||||
'Resume Refresh': '继续刷新',
|
||||
Up: '上传',
|
||||
Down: '下载',
|
||||
'Test Latency': '延迟测速',
|
||||
|
|
|
@ -9,18 +9,16 @@ export const getClashAPIConfig = (s: State) => {
|
|||
const idx = s.app.selectedClashAPIConfigIndex;
|
||||
return s.app.clashAPIConfigs[idx];
|
||||
};
|
||||
export const getSelectedClashAPIConfigIndex = (s: State) =>
|
||||
s.app.selectedClashAPIConfigIndex;
|
||||
export const getSelectedClashAPIConfigIndex = (s: State) => s.app.selectedClashAPIConfigIndex;
|
||||
export const getClashAPIConfigs = (s: State) => s.app.clashAPIConfigs;
|
||||
export const getTheme = (s: State) => s.app.theme;
|
||||
export const getSelectedChartStyleIndex = (s: State) =>
|
||||
s.app.selectedChartStyleIndex;
|
||||
export const getSelectedChartStyleIndex = (s: State) => s.app.selectedChartStyleIndex;
|
||||
export const getLatencyTestUrl = (s: State) => s.app.latencyTestUrl;
|
||||
export const getCollapsibleIsOpen = (s: State) => s.app.collapsibleIsOpen;
|
||||
export const getProxySortBy = (s: State) => s.app.proxySortBy;
|
||||
export const getHideUnavailableProxies = (s: State) =>
|
||||
s.app.hideUnavailableProxies;
|
||||
export const getHideUnavailableProxies = (s: State) => s.app.hideUnavailableProxies;
|
||||
export const getAutoCloseOldConns = (s: State) => s.app.autoCloseOldConns;
|
||||
export const getLogStreamingPaused = (s: State) => s.app.logStreamingPaused;
|
||||
|
||||
const saveStateDebounced = debounce(saveState, 600);
|
||||
|
||||
|
@ -103,7 +101,7 @@ function setTheme(theme = 'dark') {
|
|||
themeColorMeta.setAttribute('content', '#202020');
|
||||
} else {
|
||||
rootEl.setAttribute('data-theme', 'light');
|
||||
themeColorMeta.setAttribute('content', '#eeeeee');
|
||||
themeColorMeta.setAttribute('content', '#f7f7f7');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,9 +119,7 @@ export function switchTheme() {
|
|||
};
|
||||
}
|
||||
|
||||
export function selectChartStyleIndex(
|
||||
selectedChartStyleIndex: number | string
|
||||
) {
|
||||
export function selectChartStyleIndex(selectedChartStyleIndex: number | string) {
|
||||
return (dispatch: DispatchFn, getState: GetStateFn) => {
|
||||
dispatch('appSelectChartStyleIndex', (s) => {
|
||||
s.app.selectedChartStyleIndex = Number(selectedChartStyleIndex);
|
||||
|
@ -143,11 +139,7 @@ export function updateAppConfig(name: string, value: unknown) {
|
|||
};
|
||||
}
|
||||
|
||||
export function updateCollapsibleIsOpen(
|
||||
prefix: string,
|
||||
name: string,
|
||||
v: boolean
|
||||
) {
|
||||
export function updateCollapsibleIsOpen(prefix: string, name: string, v: boolean) {
|
||||
return (dispatch: DispatchFn, getState: GetStateFn) => {
|
||||
dispatch('updateCollapsibleIsOpen', (s: State) => {
|
||||
s.app.collapsibleIsOpen[`${prefix}:${name}`] = v;
|
||||
|
@ -158,9 +150,7 @@ export function updateCollapsibleIsOpen(
|
|||
}
|
||||
|
||||
const defaultClashAPIConfig = {
|
||||
baseURL:
|
||||
document.getElementById('app')?.getAttribute('data-base-url') ??
|
||||
'http://127.0.0.1:9090',
|
||||
baseURL: document.getElementById('app')?.getAttribute('data-base-url') ?? 'http://127.0.0.1:9090',
|
||||
secret: '',
|
||||
addedAt: 0,
|
||||
};
|
||||
|
@ -179,6 +169,7 @@ const defaultState: StateApp = {
|
|||
proxySortBy: 'Natural',
|
||||
hideUnavailableProxies: false,
|
||||
autoCloseOldConns: false,
|
||||
logStreamingPaused: false,
|
||||
};
|
||||
|
||||
function parseConfigQueryString() {
|
||||
|
|
|
@ -13,6 +13,7 @@ export type StateApp = {
|
|||
proxySortBy: string;
|
||||
hideUnavailableProxies: boolean;
|
||||
autoCloseOldConns: boolean;
|
||||
logStreamingPaused: boolean;
|
||||
};
|
||||
|
||||
export type ClashGeneralConfig = {
|
||||
|
|
Loading…
Reference in a new issue