Add float action button to pause/start log streaming
This commit is contained in:
parent
b1ea08a4ee
commit
ce3ed3d99f
6 changed files with 50 additions and 50 deletions
|
@ -2,6 +2,10 @@
|
|||
"name": "yacd",
|
||||
"version": "0.3.3",
|
||||
"description": "Yet another Clash dashboard",
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
"singleQuote": true
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --fix --cache src",
|
||||
"dev": "vite",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
singleQuote: true
|
||||
};
|
|
@ -5,6 +5,12 @@ import { LogsAPIConfig } from 'src/types';
|
|||
import { buildLogsWebSocketURL, getURLAndInit } from '../misc/request-helper';
|
||||
|
||||
type AppendLogFn = (x: Log) => void;
|
||||
enum WebSocketReadyState {
|
||||
Connecting = 0,
|
||||
Open = 1,
|
||||
Closing = 2,
|
||||
Closed = 3,
|
||||
}
|
||||
|
||||
const endpoint = '/logs';
|
||||
const textDecoder = new TextDecoder('utf-8');
|
||||
|
@ -86,20 +92,13 @@ function makeConnStr(c: LogsAPIConfig) {
|
|||
let prevConnStr: string;
|
||||
let controller: AbortController;
|
||||
|
||||
// 1 OPEN
|
||||
// other value CLOSED
|
||||
// similar to ws readyState but not the same
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
|
||||
let wsState: number;
|
||||
export function fetchLogs(apiConfig: LogsAPIConfig, appendLog: AppendLogFn) {
|
||||
if (apiConfig.logLevel === 'uninit') return;
|
||||
if (fetched || wsState === 1) return;
|
||||
if (fetched || (ws && ws.readyState === WebSocketReadyState.Open)) return;
|
||||
prevAppendLogFn = appendLog;
|
||||
wsState = 1;
|
||||
const url = buildLogsWebSocketURL(apiConfig, endpoint);
|
||||
ws = new WebSocket(url);
|
||||
ws.addEventListener('error', () => {
|
||||
wsState = 3;
|
||||
fetchLogsWithFetch(apiConfig, appendLog);
|
||||
});
|
||||
ws.addEventListener('message', function (event) {
|
||||
|
@ -107,10 +106,14 @@ export function fetchLogs(apiConfig: LogsAPIConfig, appendLog: AppendLogFn) {
|
|||
});
|
||||
}
|
||||
|
||||
export function stop() {
|
||||
ws.close();
|
||||
if (controller) controller.abort();
|
||||
}
|
||||
|
||||
export function reconnect(apiConfig: LogsAPIConfig) {
|
||||
if (!prevAppendLogFn || !ws) return;
|
||||
ws.close();
|
||||
wsState = 3;
|
||||
fetched = false;
|
||||
fetchLogs(apiConfig, prevAppendLogFn);
|
||||
}
|
||||
|
|
|
@ -235,11 +235,7 @@ function Conn({ apiConfig }) {
|
|||
isRefreshPaused ? <Play size={16} /> : <Pause size={16} />
|
||||
}
|
||||
mainButtonStyles={
|
||||
isRefreshPaused
|
||||
? {
|
||||
background: '#e74c3c',
|
||||
}
|
||||
: {}
|
||||
isRefreshPaused ? { background: '#e74c3c' } : {}
|
||||
}
|
||||
style={fabPosition}
|
||||
text={isRefreshPaused ? 'Resume Refresh' : 'Pause Refresh'}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import cx from 'clsx';
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
areEqual,
|
||||
FixedSizeList as List,
|
||||
ListChildComponentProps,
|
||||
} from 'react-window';
|
||||
import { fetchLogs } from 'src/api/logs';
|
||||
import { areEqual, FixedSizeList as List, ListChildComponentProps } from 'react-window';
|
||||
import { fetchLogs, stop as stopLogs, reconnect as reconnectLogs } from 'src/api/logs';
|
||||
import ContentHeader from 'src/components/ContentHeader';
|
||||
import LogSearch from 'src/components/LogSearch';
|
||||
import { connect } from 'src/components/StateProvider';
|
||||
|
@ -16,10 +12,12 @@ import { getClashAPIConfig } 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';
|
||||
|
||||
const { useCallback, memo, useEffect } = React;
|
||||
const { useState, useCallback, memo, useEffect } = React;
|
||||
|
||||
const paddingBottom = 30;
|
||||
const colors = {
|
||||
|
@ -51,23 +49,22 @@ function itemKey(index: number, data: LogLineProps[]) {
|
|||
return item.id;
|
||||
}
|
||||
|
||||
const Row = memo(
|
||||
({ index, style, data }: ListChildComponentProps<LogLineProps>) => {
|
||||
const r = data[index];
|
||||
return (
|
||||
<div style={style}>
|
||||
<LogLine {...r} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
areEqual
|
||||
);
|
||||
const Row = memo(({ index, style, data }: ListChildComponentProps<LogLineProps>) => {
|
||||
const r = data[index];
|
||||
return (
|
||||
<div style={style}>
|
||||
<LogLine {...r} />
|
||||
</div>
|
||||
);
|
||||
}, areEqual);
|
||||
|
||||
function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
||||
const appendLogInternal = useCallback(
|
||||
(log) => dispatch(appendLog(log)),
|
||||
[dispatch]
|
||||
);
|
||||
const [isRefreshPaused, setIsRefreshPaused] = useState(false);
|
||||
const toggleIsRefreshPaused = useCallback(() => {
|
||||
isRefreshPaused ? reconnectLogs({ ...apiConfig, logLevel }) : stopLogs();
|
||||
setIsRefreshPaused((x) => !x);
|
||||
}, [isRefreshPaused, apiConfig, logLevel]);
|
||||
const appendLogInternal = useCallback((log) => dispatch(appendLog(log)), [dispatch]);
|
||||
useEffect(() => {
|
||||
fetchLogs({ ...apiConfig, logLevel }, appendLogInternal);
|
||||
}, [apiConfig, logLevel, appendLogInternal]);
|
||||
|
@ -80,10 +77,7 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
|||
<LogSearch />
|
||||
<div ref={refLogsContainer} style={{ paddingBottom }}>
|
||||
{logs.length === 0 ? (
|
||||
<div
|
||||
className={s.logPlaceholder}
|
||||
style={{ height: containerHeight - paddingBottom }}
|
||||
>
|
||||
<div className={s.logPlaceholder} style={{ height: containerHeight - paddingBottom }}>
|
||||
<div className={s.logPlaceholderIcon}>
|
||||
<SvgYacd width={200} height={200} />
|
||||
</div>
|
||||
|
@ -101,6 +95,14 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
|||
>
|
||||
{Row}
|
||||
</List>
|
||||
|
||||
<Fab
|
||||
icon={isRefreshPaused ? <Play size={16} /> : <Pause size={16} />}
|
||||
mainButtonStyles={isRefreshPaused ? { background: '#e74c3c' } : {}}
|
||||
style={fabPosition}
|
||||
text={isRefreshPaused ? 'Resume Refresh' : 'Pause Refresh'}
|
||||
onClick={toggleIsRefreshPaused}
|
||||
></Fab>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -53,18 +53,14 @@
|
|||
|
||||
:root {
|
||||
--font-mono: 'Roboto Mono', Menlo, monospace;
|
||||
--font-normal: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica,
|
||||
Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
|
||||
'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
// prettier-ignore
|
||||
--font-normal: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
--color-focus-blue: #1a73e8;
|
||||
--btn-bg: #387cec;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji,
|
||||
Segoe UI Symbol, 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial,
|
||||
sans-serif;
|
||||
font-family: var(--font-normal);
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
@ -135,10 +131,12 @@ body {
|
|||
|
||||
:root[data-theme='dark'] {
|
||||
@include dark;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-theme='light'] {
|
||||
@include light;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
.flexCenter {
|
||||
|
|
Loading…
Reference in a new issue