Minor log style tweak
This commit is contained in:
parent
d4015f6423
commit
fcab7cad4f
10 changed files with 101 additions and 68 deletions
|
@ -1,14 +1,9 @@
|
||||||
import { LogsAPIConfig } from 'src/types';
|
import { pad0 } from 'src/misc/utils';
|
||||||
|
import { Log, LogsAPIConfig } from 'src/types';
|
||||||
|
|
||||||
import { buildLogsWebSocketURL, getURLAndInit } from '../misc/request-helper';
|
import { buildLogsWebSocketURL, getURLAndInit } from '../misc/request-helper';
|
||||||
|
|
||||||
type LogEntry = {
|
type AppendLogFn = (x: Log) => void;
|
||||||
time?: string;
|
|
||||||
id?: string;
|
|
||||||
even?: boolean;
|
|
||||||
// and some other props
|
|
||||||
};
|
|
||||||
type AppendLogFn = (x: LogEntry) => void;
|
|
||||||
|
|
||||||
const endpoint = '/logs';
|
const endpoint = '/logs';
|
||||||
const textDecoder = new TextDecoder('utf-8');
|
const textDecoder = new TextDecoder('utf-8');
|
||||||
|
@ -22,7 +17,7 @@ let fetched = false;
|
||||||
let decoded = '';
|
let decoded = '';
|
||||||
|
|
||||||
function appendData(s: string, callback: AppendLogFn) {
|
function appendData(s: string, callback: AppendLogFn) {
|
||||||
let o: LogEntry;
|
let o: Partial<Log>;
|
||||||
try {
|
try {
|
||||||
o = JSON.parse(s);
|
o = JSON.parse(s);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -31,7 +26,7 @@ function appendData(s: string, callback: AppendLogFn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const time = now.toLocaleString('zh-Hans');
|
const time = formatDate(now);
|
||||||
// mutate input param in place intentionally
|
// mutate input param in place intentionally
|
||||||
o.time = time;
|
o.time = time;
|
||||||
o.id = +now - 0 + getRandomStr();
|
o.id = +now - 0 + getRandomStr();
|
||||||
|
@ -39,6 +34,17 @@ function appendData(s: string, callback: AppendLogFn) {
|
||||||
callback(o);
|
callback(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDate(d: Date) {
|
||||||
|
// 19-03-09 12:45
|
||||||
|
const YY = d.getFullYear() % 100;
|
||||||
|
const MM = pad0(d.getMonth() + 1, 2);
|
||||||
|
const dd = pad0(d.getDate(), 2);
|
||||||
|
const HH = pad0(d.getHours(), 2);
|
||||||
|
const mm = pad0(d.getMinutes(), 2);
|
||||||
|
const ss = pad0(d.getSeconds(), 2);
|
||||||
|
return `${YY}-${MM}-${dd} ${HH}:${mm}:${ss}`;
|
||||||
|
}
|
||||||
|
|
||||||
function pump(reader: ReadableStreamDefaultReader, appendLog: AppendLogFn) {
|
function pump(reader: ReadableStreamDefaultReader, appendLog: AppendLogFn) {
|
||||||
return reader.read().then(({ done, value }) => {
|
return reader.read().then(({ done, value }) => {
|
||||||
const str = textDecoder.decode(value, { stream: !done });
|
const str = textDecoder.decode(value, { stream: !done });
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logType {
|
.logType {
|
||||||
|
@ -11,8 +10,7 @@
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 66px;
|
width: 66px;
|
||||||
background: green;
|
border-radius: 100px;
|
||||||
border-radius: 5px;
|
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { areEqual, FixedSizeList as List } from 'react-window';
|
import {
|
||||||
|
areEqual,
|
||||||
|
FixedSizeList as List,
|
||||||
|
ListChildComponentProps,
|
||||||
|
} from 'react-window';
|
||||||
|
import { fetchLogs } from 'src/api/logs';
|
||||||
|
import ContentHeader from 'src/components/ContentHeader';
|
||||||
|
import LogSearch from 'src/components/LogSearch';
|
||||||
|
import { connect } from 'src/components/StateProvider';
|
||||||
|
import SvgYacd from 'src/components/SvgYacd';
|
||||||
|
import useRemainingViewPortHeight from 'src/hooks/useRemainingViewPortHeight';
|
||||||
|
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 { fetchLogs } from '../api/logs';
|
import s from './Logs.module.css';
|
||||||
import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
|
|
||||||
import { getClashAPIConfig } from '../store/app';
|
|
||||||
import { getLogLevel } from '../store/configs';
|
|
||||||
import { appendLog, getLogsForDisplay } from '../store/logs';
|
|
||||||
import ContentHeader from './ContentHeader';
|
|
||||||
import s0 from './Logs.module.css';
|
|
||||||
import LogSearch from './LogSearch';
|
|
||||||
import { connect } from './StateProvider';
|
|
||||||
import SvgYacd from './SvgYacd';
|
|
||||||
|
|
||||||
const { useCallback, memo, useEffect } = React;
|
const { useCallback, memo, useEffect } = React;
|
||||||
|
|
||||||
|
@ -20,48 +25,44 @@ const paddingBottom = 30;
|
||||||
const colors = {
|
const colors = {
|
||||||
debug: 'none',
|
debug: 'none',
|
||||||
// debug: '#8a8a8a',
|
// debug: '#8a8a8a',
|
||||||
info: '#454545',
|
info: 'var(--bg-log-info-tag)',
|
||||||
// info: '#147d14',
|
|
||||||
warning: '#b99105',
|
warning: '#b99105',
|
||||||
error: '#c11c1c',
|
error: '#c11c1c',
|
||||||
};
|
};
|
||||||
|
|
||||||
type LogLineProps = {
|
type LogLineProps = Partial<Log>;
|
||||||
time?: string;
|
|
||||||
even?: boolean;
|
|
||||||
payload?: string;
|
|
||||||
type?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function LogLine({ time, even, payload, type }: LogLineProps) {
|
function LogLine({ time, even, payload, type }: LogLineProps) {
|
||||||
const className = cx({ even }, s0.log);
|
const className = cx({ even }, s.log);
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className={s0.logMeta}>
|
<div className={s.logMeta}>
|
||||||
<div className={s0.logTime}>{time}</div>
|
<div className={s.logTime}>{time}</div>
|
||||||
<div className={s0.logType} style={{ backgroundColor: colors[type] }}>
|
<div className={s.logType} style={{ backgroundColor: colors[type] }}>
|
||||||
{type}
|
{type}
|
||||||
</div>
|
</div>
|
||||||
<div className={s0.logText}>{payload}</div>
|
<div className={s.logText}>{payload}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function itemKey(index, data) {
|
function itemKey(index: number, data: LogLineProps[]) {
|
||||||
const item = data[index];
|
const item = data[index];
|
||||||
return item.id;
|
return item.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'index' does not exist on type '{ childre... Remove this comment to see the full error message
|
const Row = memo(
|
||||||
const Row = memo(({ index, style, data }) => {
|
({ index, style, data }: ListChildComponentProps<LogLineProps>) => {
|
||||||
const r = data[index];
|
const r = data[index];
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
<LogLine {...r} />
|
<LogLine {...r} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, areEqual);
|
},
|
||||||
|
areEqual
|
||||||
|
);
|
||||||
|
|
||||||
function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
||||||
const appendLogInternal = useCallback(
|
const appendLogInternal = useCallback(
|
||||||
|
@ -80,23 +81,20 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
||||||
<div>
|
<div>
|
||||||
<ContentHeader title={t('Logs')} />
|
<ContentHeader title={t('Logs')} />
|
||||||
<LogSearch />
|
<LogSearch />
|
||||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
|
|
||||||
<div ref={refLogsContainer} style={{ paddingBottom }}>
|
<div ref={refLogsContainer} style={{ paddingBottom }}>
|
||||||
{logs.length === 0 ? (
|
{logs.length === 0 ? (
|
||||||
<div
|
<div
|
||||||
className={s0.logPlaceholder}
|
className={s.logPlaceholder}
|
||||||
// @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
|
|
||||||
style={{ height: containerHeight - paddingBottom }}
|
style={{ height: containerHeight - paddingBottom }}
|
||||||
>
|
>
|
||||||
<div className={s0.logPlaceholderIcon}>
|
<div className={s.logPlaceholderIcon}>
|
||||||
<SvgYacd width={200} height={200} />
|
<SvgYacd width={200} height={200} />
|
||||||
</div>
|
</div>
|
||||||
<div>{t('no_logs')}</div>
|
<div>{t('no_logs')}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={s0.logsWrapper}>
|
<div className={s.logsWrapper}>
|
||||||
<List
|
<List
|
||||||
// @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
|
|
||||||
height={containerHeight - paddingBottom}
|
height={containerHeight - paddingBottom}
|
||||||
width="100%"
|
width="100%"
|
||||||
itemCount={logs.length}
|
itemCount={logs.length}
|
||||||
|
@ -113,7 +111,7 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapState = (s) => ({
|
const mapState = (s: State) => ({
|
||||||
logs: getLogsForDisplay(s),
|
logs: getLogsForDisplay(s),
|
||||||
logLevel: getLogLevel(s),
|
logLevel: getLogLevel(s),
|
||||||
apiConfig: getClashAPIConfig(s),
|
apiConfig: getClashAPIConfig(s),
|
||||||
|
|
|
@ -124,6 +124,7 @@ body.dark {
|
||||||
--color-btn-fg: #bebebe;
|
--color-btn-fg: #bebebe;
|
||||||
--color-bg-proxy: #303030;
|
--color-bg-proxy: #303030;
|
||||||
--color-row-odd: #282828;
|
--color-row-odd: #282828;
|
||||||
|
--bg-log-info-tag: #454545;
|
||||||
--bg-modal: #1f1f20;
|
--bg-modal: #1f1f20;
|
||||||
--bg-near-transparent: rgba(255, 255, 255, 0.1);
|
--bg-near-transparent: rgba(255, 255, 255, 0.1);
|
||||||
--bg-tooltip: #111;
|
--bg-tooltip: #111;
|
||||||
|
@ -151,6 +152,7 @@ body.light {
|
||||||
--color-btn-fg: #101010;
|
--color-btn-fg: #101010;
|
||||||
--color-bg-proxy: #fafafa;
|
--color-bg-proxy: #fafafa;
|
||||||
--color-row-odd: #f5f5f5;
|
--color-row-odd: #f5f5f5;
|
||||||
|
--bg-log-info-tag: #888;
|
||||||
--bg-modal: #fbfbfb;
|
--bg-modal: #fbfbfb;
|
||||||
--bg-near-transparent: rgba(0, 0, 0, 0.1);
|
--bg-near-transparent: rgba(0, 0, 0, 0.1);
|
||||||
--bg-tooltip: #f0f0f0;
|
--bg-tooltip: #f0f0f0;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.ToggleSwitch {
|
.ToggleSwitch {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
border-radius: 4px;
|
||||||
border: 1px solid #525252;
|
border: 1px solid #525252;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
background: var(--color-toggle-bg);
|
background: var(--color-toggle-bg);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
const { useState, useRef, useCallback, useLayoutEffect } = React;
|
const { useState, useRef, useCallback, useLayoutEffect } = React;
|
||||||
|
|
||||||
|
@ -9,8 +9,10 @@ const { useState, useRef, useCallback, useLayoutEffect } = React;
|
||||||
* to the bottom of the view port
|
* to the bottom of the view port
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default function useRemainingViewPortHeight() {
|
export default function useRemainingViewPortHeight<
|
||||||
const ref = useRef(null);
|
ElementType extends HTMLDivElement
|
||||||
|
>(): [React.MutableRefObject<ElementType>, number] {
|
||||||
|
const ref = useRef<ElementType>(null);
|
||||||
const [containerHeight, setContainerHeight] = useState(200);
|
const [containerHeight, setContainerHeight] = useState(200);
|
||||||
const updateContainerHeight = useCallback(() => {
|
const updateContainerHeight = useCallback(() => {
|
||||||
const { top } = ref.current.getBoundingClientRect();
|
const { top } = ref.current.getBoundingClientRect();
|
||||||
|
|
|
@ -12,7 +12,7 @@ function genCommonHeaders({ secret }: { secret?: string }) {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
function buildWebSocketURLBase(baseURL: string, params: URLSearchParams, endpoint: string) {
|
function buildWebSocketURLBase(baseURL: string, params: URLSearchParams, endpoint: string) {
|
||||||
let qs = '?' + params.toString()
|
const qs = '?' + params.toString()
|
||||||
const url = new URL(baseURL);
|
const url = new URL(baseURL);
|
||||||
url.protocol === 'https:' ? (url.protocol = 'wss:') : (url.protocol = 'ws:');
|
url.protocol === 'https:' ? (url.protocol = 'wss:') : (url.protocol = 'ws:');
|
||||||
return `${trimTrailingSlash(url.href)}${endpoint}${qs}`;
|
return `${trimTrailingSlash(url.href)}${endpoint}${qs}`;
|
||||||
|
|
|
@ -31,3 +31,11 @@ export function debounce<T extends any[]>(
|
||||||
export function trimTrailingSlash(s: string) {
|
export function trimTrailingSlash(s: string) {
|
||||||
return s.replace(/\/$/, '');
|
return s.replace(/\/$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pad0(number: number | string, len: number): string {
|
||||||
|
let output = String(number);
|
||||||
|
while (output.length < len) {
|
||||||
|
output = '0' + output;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { DispatchFn, GetStateFn, Log, State } from './types';
|
||||||
|
|
||||||
const LogSize = 300;
|
const LogSize = 300;
|
||||||
|
|
||||||
const getLogs = (s) => s.logs.logs;
|
const getLogs = (s: State) => s.logs.logs;
|
||||||
const getTail = (s) => s.logs.tail;
|
const getTail = (s: State) => s.logs.tail;
|
||||||
export const getSearchText = (s) => s.logs.searchText;
|
export const getSearchText = (s: State) => s.logs.searchText;
|
||||||
export const getLogsForDisplay = createSelector(
|
export const getLogsForDisplay = createSelector(
|
||||||
getLogs,
|
getLogs,
|
||||||
getTail,
|
getTail,
|
||||||
|
@ -25,16 +27,16 @@ export const getLogsForDisplay = createSelector(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export function updateSearchText(text) {
|
export function updateSearchText(text: string) {
|
||||||
return (dispatch) => {
|
return (dispatch: DispatchFn) => {
|
||||||
dispatch('logsUpdateSearchText', (s) => {
|
dispatch('logsUpdateSearchText', (s) => {
|
||||||
s.logs.searchText = text.toLowerCase();
|
s.logs.searchText = text.toLowerCase();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function appendLog(log) {
|
export function appendLog(log: Log) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch: DispatchFn, getState: GetStateFn) => {
|
||||||
const s = getState();
|
const s = getState();
|
||||||
const logs = getLogs(s);
|
const logs = getLogs(s);
|
||||||
const tailCurr = getTail(s);
|
const tailCurr = getTail(s);
|
||||||
|
@ -42,9 +44,7 @@ export function appendLog(log) {
|
||||||
// mutate intentionally for performance
|
// mutate intentionally for performance
|
||||||
logs[tail] = log;
|
logs[tail] = log;
|
||||||
|
|
||||||
dispatch('logsAppendLog', (s) => {
|
dispatch('logsAppendLog', (s: State) => (s.logs.tail = tail));
|
||||||
s.logs.tail = tail;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,22 @@ export type StateProxies = {
|
||||||
switchProxyCtx?: SwitchProxyCtx;
|
switchProxyCtx?: SwitchProxyCtx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///// store.logs
|
||||||
|
|
||||||
|
export type Log = {
|
||||||
|
time: string;
|
||||||
|
even: boolean;
|
||||||
|
payload: string;
|
||||||
|
type: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StateLogs = {
|
||||||
|
searchText: string;
|
||||||
|
logs: Log[];
|
||||||
|
tail: number;
|
||||||
|
};
|
||||||
|
|
||||||
///// store.configs
|
///// store.configs
|
||||||
|
|
||||||
export type StateConfigs = {
|
export type StateConfigs = {
|
||||||
|
@ -78,7 +94,9 @@ export type State = {
|
||||||
app: StateApp;
|
app: StateApp;
|
||||||
configs: StateConfigs;
|
configs: StateConfigs;
|
||||||
proxies: StateProxies;
|
proxies: StateProxies;
|
||||||
|
logs: StateLogs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetStateFn = () => State;
|
export type GetStateFn = () => State;
|
||||||
export interface DispatchFn {
|
export interface DispatchFn {
|
||||||
(msg: string, change: (s: State) => void): void;
|
(msg: string, change: (s: State) => void): void;
|
||||||
|
|
Loading…
Reference in a new issue