mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
@@ -17,6 +17,7 @@
|
|||||||
- 支持连接页面各个项目的排序
|
- 支持连接页面各个项目的排序
|
||||||
- 实现可选的自动备份
|
- 实现可选的自动备份
|
||||||
- 连接页面支持查看已关闭的连接(最近最多 500 个已关闭连接)
|
- 连接页面支持查看已关闭的连接(最近最多 500 个已关闭连接)
|
||||||
|
- 日志页面支持按时间倒序
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "السجلات"
|
"title": "السجلات"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "Protokolle"
|
"title": "Protokolle"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "Logs"
|
"title": "Logs"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "Registros"
|
"title": "Registros"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "لاگها"
|
"title": "لاگها"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "Log"
|
"title": "Log"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "ログ"
|
"title": "ログ"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "로그"
|
"title": "로그"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "Логи"
|
"title": "Логи"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "Günlükler"
|
"title": "Günlükler"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "Логлар"
|
"title": "Логлар"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "Newest first",
|
||||||
|
"showAscending": "Oldest first"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "日志"
|
"title": "日志"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "按时间倒序",
|
||||||
|
"showAscending": "按时间正序"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"title": "日誌"
|
"title": "日誌"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"showDescending": "按時間倒序",
|
||||||
|
"showAscending": "按時間正序"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
PlayCircleOutlineRounded,
|
PlayCircleOutlineRounded,
|
||||||
PauseCircleOutlineRounded,
|
PauseCircleOutlineRounded,
|
||||||
|
SwapVertRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { Box, Button, IconButton, MenuItem } from "@mui/material";
|
import { Box, Button, IconButton, MenuItem } from "@mui/material";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
@@ -20,6 +21,8 @@ const LogPage = () => {
|
|||||||
const [clashLog, setClashLog] = useClashLog();
|
const [clashLog, setClashLog] = useClashLog();
|
||||||
const enableLog = clashLog.enable;
|
const enableLog = clashLog.enable;
|
||||||
const logState = clashLog.logFilter;
|
const logState = clashLog.logFilter;
|
||||||
|
const logOrder = clashLog.logOrder ?? "asc";
|
||||||
|
const isDescending = logOrder === "desc";
|
||||||
|
|
||||||
const [match, setMatch] = useState(() => (_: string) => true);
|
const [match, setMatch] = useState(() => (_: string) => true);
|
||||||
const [searchState, setSearchState] = useState<SearchState>();
|
const [searchState, setSearchState] = useState<SearchState>();
|
||||||
@@ -49,6 +52,11 @@ const LogPage = () => {
|
|||||||
});
|
});
|
||||||
}, [logData, logState, match]);
|
}, [logData, logState, match]);
|
||||||
|
|
||||||
|
const filteredLogs = useMemo(
|
||||||
|
() => (isDescending ? [...filterLogs].reverse() : filterLogs),
|
||||||
|
[filterLogs, isDescending],
|
||||||
|
);
|
||||||
|
|
||||||
const handleLogLevelChange = (newLevel: string) => {
|
const handleLogLevelChange = (newLevel: string) => {
|
||||||
setClashLog((pre: any) => ({ ...pre, logFilter: newLevel }));
|
setClashLog((pre: any) => ({ ...pre, logFilter: newLevel }));
|
||||||
};
|
};
|
||||||
@@ -57,6 +65,13 @@ const LogPage = () => {
|
|||||||
setClashLog((pre: any) => ({ ...pre, enable: !enableLog }));
|
setClashLog((pre: any) => ({ ...pre, enable: !enableLog }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleToggleOrder = () => {
|
||||||
|
setClashLog((pre: any) => ({
|
||||||
|
...pre,
|
||||||
|
logOrder: pre.logOrder === "desc" ? "asc" : "desc",
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage
|
<BasePage
|
||||||
full
|
full
|
||||||
@@ -86,6 +101,28 @@ const LogPage = () => {
|
|||||||
<PlayCircleOutlineRounded />
|
<PlayCircleOutlineRounded />
|
||||||
)}
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
title={t(
|
||||||
|
isDescending
|
||||||
|
? "logs.actions.showAscending"
|
||||||
|
: "logs.actions.showDescending",
|
||||||
|
)}
|
||||||
|
aria-label={t(
|
||||||
|
isDescending
|
||||||
|
? "logs.actions.showAscending"
|
||||||
|
: "logs.actions.showDescending",
|
||||||
|
)}
|
||||||
|
size="small"
|
||||||
|
color="inherit"
|
||||||
|
onClick={handleToggleOrder}
|
||||||
|
>
|
||||||
|
<SwapVertRounded
|
||||||
|
sx={{
|
||||||
|
transform: isDescending ? "scaleY(-1)" : "none",
|
||||||
|
transition: "transform 0.2s ease",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -129,17 +166,17 @@ const LogPage = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{filterLogs.length > 0 ? (
|
{filteredLogs.length > 0 ? (
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
initialTopMostItemIndex={999}
|
initialTopMostItemIndex={isDescending ? 0 : 999}
|
||||||
data={filterLogs}
|
data={filteredLogs}
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
itemContent={(index, item) => (
|
itemContent={(index, item) => (
|
||||||
<LogItem value={item} searchState={searchState} />
|
<LogItem value={item} searchState={searchState} />
|
||||||
)}
|
)}
|
||||||
followOutput={"smooth"}
|
followOutput={isDescending ? false : "smooth"}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<BaseEmpty />
|
<BaseEmpty />
|
||||||
|
|||||||
@@ -7,16 +7,19 @@ const [ThemeModeProvider, useThemeMode, useSetThemeMode] = createContextState<
|
|||||||
>("light");
|
>("light");
|
||||||
|
|
||||||
export type LogFilter = "all" | "debug" | "info" | "warn" | "err";
|
export type LogFilter = "all" | "debug" | "info" | "warn" | "err";
|
||||||
|
export type LogOrder = "asc" | "desc";
|
||||||
|
|
||||||
interface IClashLog {
|
interface IClashLog {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
logLevel: LogLevel;
|
logLevel: LogLevel;
|
||||||
logFilter: LogFilter;
|
logFilter: LogFilter;
|
||||||
|
logOrder: LogOrder;
|
||||||
}
|
}
|
||||||
const defaultClashLog: IClashLog = {
|
const defaultClashLog: IClashLog = {
|
||||||
enable: true,
|
enable: true,
|
||||||
logLevel: "info",
|
logLevel: "info",
|
||||||
logFilter: "all",
|
logFilter: "all",
|
||||||
|
logOrder: "asc",
|
||||||
};
|
};
|
||||||
export const useClashLog = () =>
|
export const useClashLog = () =>
|
||||||
useLocalStorage<IClashLog>("clash-log", defaultClashLog, {
|
useLocalStorage<IClashLog>("clash-log", defaultClashLog, {
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ export const translationKeys = [
|
|||||||
"layout.components.navigation.menu.unlock",
|
"layout.components.navigation.menu.unlock",
|
||||||
"layout.components.navigation.menu.lock",
|
"layout.components.navigation.menu.lock",
|
||||||
"logs.page.title",
|
"logs.page.title",
|
||||||
|
"logs.actions.showDescending",
|
||||||
|
"logs.actions.showAscending",
|
||||||
"profiles.page.actions.updateAll",
|
"profiles.page.actions.updateAll",
|
||||||
"profiles.page.actions.viewRuntimeConfig",
|
"profiles.page.actions.viewRuntimeConfig",
|
||||||
"profiles.page.actions.reactivate",
|
"profiles.page.actions.reactivate",
|
||||||
|
|||||||
@@ -197,6 +197,10 @@ export interface TranslationResources {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
logs: {
|
logs: {
|
||||||
|
actions: {
|
||||||
|
showAscending: string;
|
||||||
|
showDescending: string;
|
||||||
|
};
|
||||||
page: {
|
page: {
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user