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';
|
||||
|
||||
type LogEntry = {
|
||||
time?: string;
|
||||
id?: string;
|
||||
even?: boolean;
|
||||
// and some other props
|
||||
};
|
||||
type AppendLogFn = (x: LogEntry) => void;
|
||||
type AppendLogFn = (x: Log) => void;
|
||||
|
||||
const endpoint = '/logs';
|
||||
const textDecoder = new TextDecoder('utf-8');
|
||||
|
@ -22,7 +17,7 @@ let fetched = false;
|
|||
let decoded = '';
|
||||
|
||||
function appendData(s: string, callback: AppendLogFn) {
|
||||
let o: LogEntry;
|
||||
let o: Partial<Log>;
|
||||
try {
|
||||
o = JSON.parse(s);
|
||||
} catch (err) {
|
||||
|
@ -31,7 +26,7 @@ function appendData(s: string, callback: AppendLogFn) {
|
|||
}
|
||||
|
||||
const now = new Date();
|
||||
const time = now.toLocaleString('zh-Hans');
|
||||
const time = formatDate(now);
|
||||
// mutate input param in place intentionally
|
||||
o.time = time;
|
||||
o.id = +now - 0 + getRandomStr();
|
||||
|
@ -39,6 +34,17 @@ function appendData(s: string, callback: AppendLogFn) {
|
|||
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) {
|
||||
return reader.read().then(({ done, value }) => {
|
||||
const str = textDecoder.decode(value, { stream: !done });
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.9em;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.logType {
|
||||
|
@ -11,8 +10,7 @@
|
|||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
width: 66px;
|
||||
background: green;
|
||||
border-radius: 5px;
|
||||
border-radius: 100px;
|
||||
padding: 3px 5px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
import cx from 'clsx';
|
||||
import * as React from 'react';
|
||||
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 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';
|
||||
import s from './Logs.module.css';
|
||||
|
||||
const { useCallback, memo, useEffect } = React;
|
||||
|
||||
|
@ -20,48 +25,44 @@ const paddingBottom = 30;
|
|||
const colors = {
|
||||
debug: 'none',
|
||||
// debug: '#8a8a8a',
|
||||
info: '#454545',
|
||||
// info: '#147d14',
|
||||
info: 'var(--bg-log-info-tag)',
|
||||
warning: '#b99105',
|
||||
error: '#c11c1c',
|
||||
};
|
||||
|
||||
type LogLineProps = {
|
||||
time?: string;
|
||||
even?: boolean;
|
||||
payload?: string;
|
||||
type?: string;
|
||||
};
|
||||
type LogLineProps = Partial<Log>;
|
||||
|
||||
function LogLine({ time, even, payload, type }: LogLineProps) {
|
||||
const className = cx({ even }, s0.log);
|
||||
const className = cx({ even }, s.log);
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={s0.logMeta}>
|
||||
<div className={s0.logTime}>{time}</div>
|
||||
<div className={s0.logType} style={{ backgroundColor: colors[type] }}>
|
||||
<div className={s.logMeta}>
|
||||
<div className={s.logTime}>{time}</div>
|
||||
<div className={s.logType} style={{ backgroundColor: colors[type] }}>
|
||||
{type}
|
||||
</div>
|
||||
<div className={s0.logText}>{payload}</div>
|
||||
<div className={s.logText}>{payload}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function itemKey(index, data) {
|
||||
function itemKey(index: number, data: LogLineProps[]) {
|
||||
const item = data[index];
|
||||
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(({ index, style, data }) => {
|
||||
const Row = memo(
|
||||
({ index, style, data }: ListChildComponentProps<LogLineProps>) => {
|
||||
const r = data[index];
|
||||
return (
|
||||
<div style={style}>
|
||||
<LogLine {...r} />
|
||||
</div>
|
||||
);
|
||||
}, areEqual);
|
||||
},
|
||||
areEqual
|
||||
);
|
||||
|
||||
function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
||||
const appendLogInternal = useCallback(
|
||||
|
@ -80,23 +81,20 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
|||
<div>
|
||||
<ContentHeader title={t('Logs')} />
|
||||
<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 }}>
|
||||
{logs.length === 0 ? (
|
||||
<div
|
||||
className={s0.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
|
||||
className={s.logPlaceholder}
|
||||
style={{ height: containerHeight - paddingBottom }}
|
||||
>
|
||||
<div className={s0.logPlaceholderIcon}>
|
||||
<div className={s.logPlaceholderIcon}>
|
||||
<SvgYacd width={200} height={200} />
|
||||
</div>
|
||||
<div>{t('no_logs')}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={s0.logsWrapper}>
|
||||
<div className={s.logsWrapper}>
|
||||
<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}
|
||||
width="100%"
|
||||
itemCount={logs.length}
|
||||
|
@ -113,7 +111,7 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
|||
);
|
||||
}
|
||||
|
||||
const mapState = (s) => ({
|
||||
const mapState = (s: State) => ({
|
||||
logs: getLogsForDisplay(s),
|
||||
logLevel: getLogLevel(s),
|
||||
apiConfig: getClashAPIConfig(s),
|
||||
|
|
|
@ -124,6 +124,7 @@ body.dark {
|
|||
--color-btn-fg: #bebebe;
|
||||
--color-bg-proxy: #303030;
|
||||
--color-row-odd: #282828;
|
||||
--bg-log-info-tag: #454545;
|
||||
--bg-modal: #1f1f20;
|
||||
--bg-near-transparent: rgba(255, 255, 255, 0.1);
|
||||
--bg-tooltip: #111;
|
||||
|
@ -151,6 +152,7 @@ body.light {
|
|||
--color-btn-fg: #101010;
|
||||
--color-bg-proxy: #fafafa;
|
||||
--color-row-odd: #f5f5f5;
|
||||
--bg-log-info-tag: #888;
|
||||
--bg-modal: #fbfbfb;
|
||||
--bg-near-transparent: rgba(0, 0, 0, 0.1);
|
||||
--bg-tooltip: #f0f0f0;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.ToggleSwitch {
|
||||
user-select: none;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #525252;
|
||||
color: var(--color-text);
|
||||
background: var(--color-toggle-bg);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
const { useState, useRef, useCallback, useLayoutEffect } = React;
|
||||
|
||||
|
@ -9,8 +9,10 @@ const { useState, useRef, useCallback, useLayoutEffect } = React;
|
|||
* to the bottom of the view port
|
||||
*
|
||||
*/
|
||||
export default function useRemainingViewPortHeight() {
|
||||
const ref = useRef(null);
|
||||
export default function useRemainingViewPortHeight<
|
||||
ElementType extends HTMLDivElement
|
||||
>(): [React.MutableRefObject<ElementType>, number] {
|
||||
const ref = useRef<ElementType>(null);
|
||||
const [containerHeight, setContainerHeight] = useState(200);
|
||||
const updateContainerHeight = useCallback(() => {
|
||||
const { top } = ref.current.getBoundingClientRect();
|
||||
|
|
|
@ -12,7 +12,7 @@ function genCommonHeaders({ secret }: { secret?: string }) {
|
|||
return h;
|
||||
}
|
||||
function buildWebSocketURLBase(baseURL: string, params: URLSearchParams, endpoint: string) {
|
||||
let qs = '?' + params.toString()
|
||||
const qs = '?' + params.toString()
|
||||
const url = new URL(baseURL);
|
||||
url.protocol === 'https:' ? (url.protocol = 'wss:') : (url.protocol = 'ws:');
|
||||
return `${trimTrailingSlash(url.href)}${endpoint}${qs}`;
|
||||
|
|
|
@ -31,3 +31,11 @@ export function debounce<T extends any[]>(
|
|||
export function trimTrailingSlash(s: string) {
|
||||
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 { DispatchFn, GetStateFn, Log, State } from './types';
|
||||
|
||||
const LogSize = 300;
|
||||
|
||||
const getLogs = (s) => s.logs.logs;
|
||||
const getTail = (s) => s.logs.tail;
|
||||
export const getSearchText = (s) => s.logs.searchText;
|
||||
const getLogs = (s: State) => s.logs.logs;
|
||||
const getTail = (s: State) => s.logs.tail;
|
||||
export const getSearchText = (s: State) => s.logs.searchText;
|
||||
export const getLogsForDisplay = createSelector(
|
||||
getLogs,
|
||||
getTail,
|
||||
|
@ -25,16 +27,16 @@ export const getLogsForDisplay = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export function updateSearchText(text) {
|
||||
return (dispatch) => {
|
||||
export function updateSearchText(text: string) {
|
||||
return (dispatch: DispatchFn) => {
|
||||
dispatch('logsUpdateSearchText', (s) => {
|
||||
s.logs.searchText = text.toLowerCase();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function appendLog(log) {
|
||||
return (dispatch, getState) => {
|
||||
export function appendLog(log: Log) {
|
||||
return (dispatch: DispatchFn, getState: GetStateFn) => {
|
||||
const s = getState();
|
||||
const logs = getLogs(s);
|
||||
const tailCurr = getTail(s);
|
||||
|
@ -42,9 +44,7 @@ export function appendLog(log) {
|
|||
// mutate intentionally for performance
|
||||
logs[tail] = log;
|
||||
|
||||
dispatch('logsAppendLog', (s) => {
|
||||
s.logs.tail = tail;
|
||||
});
|
||||
dispatch('logsAppendLog', (s: State) => (s.logs.tail = tail));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,22 @@ export type StateProxies = {
|
|||
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
|
||||
|
||||
export type StateConfigs = {
|
||||
|
@ -78,7 +94,9 @@ export type State = {
|
|||
app: StateApp;
|
||||
configs: StateConfigs;
|
||||
proxies: StateProxies;
|
||||
logs: StateLogs;
|
||||
};
|
||||
|
||||
export type GetStateFn = () => State;
|
||||
export interface DispatchFn {
|
||||
(msg: string, change: (s: State) => void): void;
|
||||
|
|
Loading…
Reference in a new issue