Compare commits
20 Commits
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
release:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
os: [windows-latest, ubuntu-18.04, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: |
|
||||
startsWith(github.repository, 'zzzgydi') &&
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
node-version: 14
|
||||
|
||||
- name: Install Dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: startsWith(matrix.os, 'ubuntu-')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libappindicator3-dev librsvg2-dev libayatana-appindicator3-dev
|
||||
|
||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -12,6 +12,14 @@ on:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- ubuntu-18.04
|
||||
- ubuntu-20.04
|
||||
- ubuntu-22.04
|
||||
- macos-10.15
|
||||
- macos-11
|
||||
- macos-12
|
||||
- windows-2019
|
||||
- windows-2022
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -43,7 +51,7 @@ jobs:
|
||||
node-version: 14
|
||||
|
||||
- name: Install Dependencies (ubuntu only)
|
||||
if: github.event.inputs.os == 'ubuntu-latest'
|
||||
if: startsWith(github.event.inputs.os, 'ubuntu-')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libappindicator3-dev librsvg2-dev libayatana-appindicator3-dev
|
||||
|
||||
13
UPDATELOG.md
13
UPDATELOG.md
@@ -1,3 +1,16 @@
|
||||
## v1.0.6
|
||||
|
||||
### Features
|
||||
|
||||
- update clash and clash.meta
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- only script profile display console
|
||||
- automatic configuration update on demand at launch
|
||||
|
||||
---
|
||||
|
||||
## v1.0.5
|
||||
|
||||
### Features
|
||||
|
||||
34
package.json
34
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
@@ -18,47 +18,47 @@
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/styled": "^11.9.3",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/material": "^5.9.0",
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@emotion/styled": "^11.10.4",
|
||||
"@mui/icons-material": "^5.10.3",
|
||||
"@mui/material": "^5.10.3",
|
||||
"@tauri-apps/api": "^1.0.2",
|
||||
"ahooks": "^3.5.2",
|
||||
"ahooks": "^3.7.0",
|
||||
"axios": "^0.27.2",
|
||||
"dayjs": "^1.11.3",
|
||||
"i18next": "^21.8.12",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"dayjs": "^1.11.5",
|
||||
"i18next": "^21.9.1",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^11.17.4",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-virtuoso": "^2.16.1",
|
||||
"recoil": "^0.7.4",
|
||||
"react-virtuoso": "^2.17.2",
|
||||
"recoil": "^0.7.5",
|
||||
"snarkdown": "^2.0.0",
|
||||
"swr": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^5.0.3",
|
||||
"@tauri-apps/cli": "^1.0.4",
|
||||
"@tauri-apps/cli": "^1.0.5",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@types/lodash": "^4.14.180",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@vitejs/plugin-react": "^1.3.2",
|
||||
"@vitejs/plugin-react": "^2.0.1",
|
||||
"adm-zip": "^0.5.9",
|
||||
"cross-env": "^7.0.3",
|
||||
"fs-extra": "^10.0.0",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"husky": "^7.0.0",
|
||||
"node-fetch": "^3.2.6",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier": "^2.7.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"sass": "^1.53.0",
|
||||
"sass": "^1.54.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^2.9.13",
|
||||
"vite": "^3.0.9",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-svgr": "^2.2.0"
|
||||
"vite-plugin-svgr": "^2.2.1"
|
||||
},
|
||||
"prettier": {
|
||||
"tabWidth": 2,
|
||||
|
||||
@@ -20,7 +20,7 @@ function resolveClash() {
|
||||
|
||||
const CLASH_URL_PREFIX =
|
||||
"https://github.com/Dreamacro/clash/releases/download/premium/";
|
||||
const CLASH_LATEST_DATE = "2022.07.07";
|
||||
const CLASH_LATEST_DATE = "2022.08.26";
|
||||
|
||||
// todo
|
||||
const map = {
|
||||
@@ -52,7 +52,7 @@ async function resolveClashMeta() {
|
||||
const { platform, arch } = process;
|
||||
|
||||
const urlPrefix = `https://github.com/MetaCubeX/Clash.Meta/releases/download/`;
|
||||
const latestVersion = "v1.12.0";
|
||||
const latestVersion = "v1.13.1";
|
||||
|
||||
const map = {
|
||||
"win32-x64": "Clash.Meta-windows-amd64",
|
||||
|
||||
5
src-tauri/Cargo.lock
generated
5
src-tauri/Cargo.lock
generated
@@ -238,10 +238,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "auto-launch"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f0ab475f9b1049a9d4e9c83f45ff27ec3bce4a6cb8e58e7f29030f8992af8c5"
|
||||
checksum = "642d13324da4df30a472026356a7fd24845d4a8038e5c47ed99c62074b526fa5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dirs 4.0.0",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
@@ -25,7 +25,7 @@ nanoid = "0.4.0"
|
||||
chrono = "0.4.19"
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.8"
|
||||
auto-launch = "0.2"
|
||||
auto-launch = "0.3"
|
||||
port_scanner = "0.1.5"
|
||||
delay_timer = "0.11.1"
|
||||
parking_lot = "0.12.0"
|
||||
|
||||
@@ -68,6 +68,10 @@ impl ClashInfo {
|
||||
Some(val_str) => {
|
||||
if val_str.starts_with(":") {
|
||||
Some(format!("127.0.0.1{val_str}"))
|
||||
} else if val_str.starts_with("0.0.0.0:") {
|
||||
Some(format!("127.0.0.1:{}", &val_str[8..]))
|
||||
} else if val_str.starts_with("[::]:") {
|
||||
Some(format!("127.0.0.1:{}", &val_str[5..]))
|
||||
} else {
|
||||
Some(val_str.into())
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ impl Core {
|
||||
// timer initialize
|
||||
let mut timer = self.timer.lock();
|
||||
timer.set_core(self.clone());
|
||||
log_if_err!(timer.refresh());
|
||||
log_if_err!(timer.restore());
|
||||
}
|
||||
|
||||
/// save the window instance
|
||||
|
||||
@@ -121,7 +121,7 @@ impl Profiles {
|
||||
}
|
||||
}
|
||||
|
||||
bail!("failed to get the item by \"{}\"", uid);
|
||||
bail!("failed to get the profile item \"uid:{uid}\"");
|
||||
}
|
||||
|
||||
/// append new item
|
||||
@@ -178,7 +178,7 @@ impl Profiles {
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
bail!("failed to found the uid \"{uid}\"")
|
||||
bail!("failed to find the profile item \"uid:{uid}\"")
|
||||
}
|
||||
|
||||
/// be used to update the remote item
|
||||
@@ -286,7 +286,7 @@ impl Profiles {
|
||||
return Ok(config::read_yaml::<Mapping>(file_path.clone()));
|
||||
}
|
||||
}
|
||||
bail!("failed to found the uid \"{current}\"");
|
||||
bail!("failed to find current profile \"uid:{current}\"");
|
||||
}
|
||||
|
||||
/// generate the data for activate clash config
|
||||
|
||||
@@ -123,7 +123,7 @@ impl Sysopt {
|
||||
let auto = AutoLaunchBuilder::new()
|
||||
.set_app_name(app_name)
|
||||
.set_app_path(app_path)
|
||||
.build();
|
||||
.build()?;
|
||||
|
||||
if let Some(enable) = enable {
|
||||
// fix issue #26
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::Core;
|
||||
use crate::log_if_err;
|
||||
use crate::utils::help::get_now;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
|
||||
use std::collections::HashMap;
|
||||
@@ -63,6 +64,34 @@ impl Timer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// restore timer
|
||||
pub fn restore(&mut self) -> Result<()> {
|
||||
self.refresh()?;
|
||||
|
||||
let cur_timestamp = get_now(); // seconds
|
||||
let profiles = self.core.as_ref().unwrap().profiles.lock();
|
||||
|
||||
profiles
|
||||
.get_items()
|
||||
.unwrap_or(&vec![])
|
||||
.iter()
|
||||
.filter(|item| item.uid.is_some() && item.updated.is_some() && item.option.is_some())
|
||||
.filter(|item| {
|
||||
// mins to seconds
|
||||
let interval = item.option.as_ref().unwrap().update_interval.unwrap_or(0) as usize * 60;
|
||||
let updated = item.updated.unwrap();
|
||||
return interval > 0 && cur_timestamp - updated >= interval;
|
||||
})
|
||||
.for_each(|item| {
|
||||
let uid = item.uid.as_ref().unwrap();
|
||||
if let Some((task_id, _)) = self.timer_map.get(uid) {
|
||||
log_if_err!(self.delay_timer.advance_task(*task_id));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// generate a uid -> update_interval map
|
||||
fn gen_map(&self) -> HashMap<String, u64> {
|
||||
let profiles = self.core.as_ref().unwrap().profiles.lock();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "Clash Verge",
|
||||
"version": "1.0.5"
|
||||
"version": "1.0.6"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTheme } from "@mui/material";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
|
||||
const maxPoint = 30;
|
||||
|
||||
@@ -72,28 +70,7 @@ const TrafficGraph = (props: Props) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// reduce the GPU usage when hidden
|
||||
const [enablePaint, setEnablePaint] = useState(true);
|
||||
useEffect(() => {
|
||||
appWindow.isVisible().then(setEnablePaint);
|
||||
|
||||
const unlistenBlur = listen("tauri://blur", async () => {
|
||||
setEnablePaint(await appWindow.isVisible());
|
||||
});
|
||||
|
||||
const unlistenFocus = listen("tauri://focus", async () => {
|
||||
setEnablePaint(await appWindow.isVisible());
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlistenBlur.then((fn) => fn());
|
||||
unlistenFocus.then((fn) => fn());
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enablePaint) return;
|
||||
|
||||
let raf = 0;
|
||||
const canvas = canvasRef.current!;
|
||||
|
||||
@@ -216,7 +193,7 @@ const TrafficGraph = (props: Props) => {
|
||||
return () => {
|
||||
cancelAnimationFrame(raf);
|
||||
};
|
||||
}, [enablePaint, palette]);
|
||||
}, [palette]);
|
||||
|
||||
return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import useSWR from "swr";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Box, Grid, IconButton, Stack } from "@mui/material";
|
||||
import { RestartAltRounded } from "@mui/icons-material";
|
||||
import {
|
||||
@@ -20,6 +21,7 @@ interface Props {
|
||||
const EnhancedMode = (props: Props) => {
|
||||
const { items, chain } = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { mutate: mutateProfiles } = useSWR("getProfiles", getProfiles);
|
||||
const { data: chainLogs = {}, mutate: mutateLogs } = useSWR(
|
||||
"getRuntimeLogs",
|
||||
@@ -96,7 +98,7 @@ const EnhancedMode = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title="refresh enhanced profiles"
|
||||
title={t("Refresh profiles")}
|
||||
onClick={onEnhance}
|
||||
>
|
||||
<RestartAltRounded />
|
||||
|
||||
@@ -82,7 +82,7 @@ const InfoEditor = (props: Props) => {
|
||||
<TextField
|
||||
{...textFieldProps}
|
||||
disabled
|
||||
label="Type"
|
||||
label={t("Type")}
|
||||
value={type}
|
||||
sx={{ input: { textTransform: "capitalize" } }}
|
||||
/>
|
||||
@@ -90,7 +90,7 @@ const InfoEditor = (props: Props) => {
|
||||
<TextField
|
||||
{...textFieldProps}
|
||||
autoFocus
|
||||
label="Name"
|
||||
label={t("Name")}
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ name: e.target.value })}
|
||||
onKeyDown={(e) => e.key === "Enter" && onUpdate()}
|
||||
@@ -98,7 +98,7 @@ const InfoEditor = (props: Props) => {
|
||||
|
||||
<TextField
|
||||
{...textFieldProps}
|
||||
label="Descriptions"
|
||||
label={t("Descriptions")}
|
||||
value={form.desc}
|
||||
onChange={(e) => setForm({ desc: e.target.value })}
|
||||
onKeyDown={(e) => e.key === "Enter" && onUpdate()}
|
||||
@@ -107,7 +107,7 @@ const InfoEditor = (props: Props) => {
|
||||
{type === "remote" && (
|
||||
<TextField
|
||||
{...textFieldProps}
|
||||
label="Subscription URL"
|
||||
label={t("Subscription URL")}
|
||||
value={form.url}
|
||||
onChange={(e) => setForm({ url: e.target.value })}
|
||||
onKeyDown={(e) => e.key === "Enter" && onUpdate()}
|
||||
@@ -128,7 +128,7 @@ const InfoEditor = (props: Props) => {
|
||||
{((type === "remote" && showOpt) || type === "local") && (
|
||||
<TextField
|
||||
{...textFieldProps}
|
||||
label="Update Interval (mins)"
|
||||
label={t("Update Interval(mins)")}
|
||||
value={option.update_interval}
|
||||
onChange={(e) => {
|
||||
const str = e.target.value?.replace(/\D/, "");
|
||||
|
||||
@@ -144,7 +144,7 @@ const ProfileMore = (props: Props) => {
|
||||
</Box>
|
||||
|
||||
<Box sx={boxStyle}>
|
||||
{selected ? (
|
||||
{selected && type === "script" ? (
|
||||
hasError ? (
|
||||
<Badge color="error" variant="dot" overlap="circular">
|
||||
<IconButton
|
||||
|
||||
@@ -91,7 +91,7 @@ const ProfileNew = (props: Props) => {
|
||||
<InputLabel>Type</InputLabel>
|
||||
<Select
|
||||
autoFocus
|
||||
label="Type"
|
||||
label={t("Type")}
|
||||
value={form.type}
|
||||
onChange={(e) => setForm({ type: e.target.value })}
|
||||
>
|
||||
@@ -104,7 +104,7 @@ const ProfileNew = (props: Props) => {
|
||||
|
||||
<TextField
|
||||
{...textFieldProps}
|
||||
label="Name"
|
||||
label={t("Name")}
|
||||
autoComplete="off"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ name: e.target.value })}
|
||||
@@ -112,7 +112,7 @@ const ProfileNew = (props: Props) => {
|
||||
|
||||
<TextField
|
||||
{...textFieldProps}
|
||||
label="Descriptions"
|
||||
label={t("Descriptions")}
|
||||
autoComplete="off"
|
||||
value={form.desc}
|
||||
onChange={(e) => setForm({ desc: e.target.value })}
|
||||
@@ -121,7 +121,7 @@ const ProfileNew = (props: Props) => {
|
||||
{form.type === "remote" && (
|
||||
<TextField
|
||||
{...textFieldProps}
|
||||
label="Subscription URL"
|
||||
label={t("Subscription URL")}
|
||||
autoComplete="off"
|
||||
value={form.url}
|
||||
onChange={(e) => setForm({ url: e.target.value })}
|
||||
|
||||
@@ -97,7 +97,7 @@ const ProxyGroup = ({ group }: Props) => {
|
||||
const names = sortedProxies.map((p) => p.name);
|
||||
const groupName = group.name;
|
||||
|
||||
await delayManager.checkListDelay({ names, groupName, skipNum: 8 }, () =>
|
||||
await delayManager.checkListDelay({ names, groupName, skipNum: 16 }, () =>
|
||||
mutate("getProxies")
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Box, IconButton, TextField, SxProps } from "@mui/material";
|
||||
import {
|
||||
AccessTimeRounded,
|
||||
@@ -31,11 +32,13 @@ const ProxyHead = (props: Props) => {
|
||||
|
||||
const { showType, sortType, filterText, textState, testUrl } = headState;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [autoFocus, setAutoFocus] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// fix the focus conflict
|
||||
setTimeout(() => setAutoFocus(true), 100);
|
||||
const timer = setTimeout(() => setAutoFocus(true), 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -46,8 +49,8 @@ const ProxyHead = (props: Props) => {
|
||||
<Box sx={{ display: "flex", alignItems: "center", ...sx }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
title="location"
|
||||
color="inherit"
|
||||
title={t("Location")}
|
||||
onClick={props.onLocation}
|
||||
>
|
||||
<MyLocationRounded />
|
||||
@@ -56,7 +59,7 @@ const ProxyHead = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title="delay check"
|
||||
title={t("Delay check")}
|
||||
onClick={() => {
|
||||
// Remind the user that it is custom test url
|
||||
if (testUrl?.trim() && textState !== "filter") {
|
||||
@@ -71,7 +74,11 @@ const ProxyHead = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title={["sort by default", "sort by delay", "sort by name"][sortType]}
|
||||
title={
|
||||
[t("Sort by default"), t("Sort by delay"), t("Sort by name")][
|
||||
sortType
|
||||
]
|
||||
}
|
||||
onClick={() =>
|
||||
onHeadState({ sortType: ((sortType + 1) % 3) as ProxySortType })
|
||||
}
|
||||
@@ -84,7 +91,7 @@ const ProxyHead = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title="edit test url"
|
||||
title={t("Delay check URL")}
|
||||
onClick={() =>
|
||||
onHeadState({ textState: textState === "url" ? null : "url" })
|
||||
}
|
||||
@@ -99,7 +106,7 @@ const ProxyHead = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title="proxy detail"
|
||||
title={t("Proxy detail")}
|
||||
onClick={() => onHeadState({ showType: !showType })}
|
||||
>
|
||||
{showType ? <VisibilityRounded /> : <VisibilityOffRounded />}
|
||||
@@ -108,7 +115,7 @@ const ProxyHead = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title="filter"
|
||||
title={t("Filter")}
|
||||
onClick={() =>
|
||||
onHeadState({ textState: textState === "filter" ? null : "filter" })
|
||||
}
|
||||
@@ -127,7 +134,7 @@ const ProxyHead = (props: Props) => {
|
||||
value={filterText}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
placeholder="Filter conditions"
|
||||
placeholder={t("Filter conditions")}
|
||||
onChange={(e) => onHeadState({ filterText: e.target.value })}
|
||||
sx={{ ml: 0.5, flex: "1 1 auto", input: { py: 0.65, px: 1 } }}
|
||||
/>
|
||||
@@ -142,7 +149,7 @@ const ProxyHead = (props: Props) => {
|
||||
value={testUrl}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
placeholder="Test url"
|
||||
placeholder={t("Delay check URL")}
|
||||
onChange={(e) => onHeadState({ testUrl: e.target.value })}
|
||||
sx={{ ml: 0.5, flex: "1 1 auto", input: { py: 0.65, px: 1 } }}
|
||||
/>
|
||||
|
||||
@@ -34,6 +34,23 @@
|
||||
"To Top": "To Top",
|
||||
"To End": "To End",
|
||||
|
||||
"Location": "Location",
|
||||
"Delay check": "Delay check",
|
||||
"Sort by default": "Sort by default",
|
||||
"Sort by delay": "Sort by delay",
|
||||
"Sort by name": "Sort by name",
|
||||
"Delay check URL": "Delay check URL",
|
||||
"Proxy detail": "Proxy detail",
|
||||
"Filter": "Filter",
|
||||
"Filter conditions": "Filter conditions",
|
||||
"Refresh profiles": "Refresh profiles",
|
||||
|
||||
"Type": "Type",
|
||||
"Name": "Name",
|
||||
"Descriptions": "Descriptions",
|
||||
"Subscription URL": "Subscription URL",
|
||||
"Update Interval(mins)": "Update Interval(mins)",
|
||||
|
||||
"Settings": "Settings",
|
||||
"Clash Setting": "Clash Setting",
|
||||
"System Setting": "System Setting",
|
||||
|
||||
@@ -34,6 +34,23 @@
|
||||
"To Top": "移到最前",
|
||||
"To End": "移到末尾",
|
||||
|
||||
"Location": "当前节点",
|
||||
"Delay check": "延迟测试",
|
||||
"Sort by default": "默认排序",
|
||||
"Sort by delay": "按延迟排序",
|
||||
"Sort by name": "按名称排序",
|
||||
"Delay check URL": "延迟测试链接",
|
||||
"Proxy detail": "展示节点细节",
|
||||
"Filter": "过滤节点",
|
||||
"Filter conditions": "过滤条件",
|
||||
"Refresh profiles": "刷新配置",
|
||||
|
||||
"Type": "类型",
|
||||
"Name": "名称",
|
||||
"Descriptions": "描述",
|
||||
"Subscription URL": "订阅链接",
|
||||
"Update Interval(mins)": "更新间隔(分钟)",
|
||||
|
||||
"Settings": "设置",
|
||||
"Clash Setting": "Clash 设置",
|
||||
"System Setting": "系统设置",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { closeAllConnections, getInformation } from "@/services/api";
|
||||
import BasePage from "@/components/base/base-page";
|
||||
import ConnectionItem from "@/components/connection/connection-item";
|
||||
import BaseEmpty from "@/components/base/base-empty";
|
||||
|
||||
const initConn = { uploadTotal: 0, downloadTotal: 0, connections: [] };
|
||||
|
||||
@@ -114,7 +115,7 @@ const ConnectionsPage = () => {
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
variant="outlined"
|
||||
placeholder="Filter conditions"
|
||||
placeholder={t("Filter conditions")}
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
sx={{ input: { py: 0.65, px: 1.25 } }}
|
||||
@@ -122,10 +123,14 @@ const ConnectionsPage = () => {
|
||||
</Box>
|
||||
|
||||
<Box height="calc(100% - 50px)">
|
||||
<Virtuoso
|
||||
data={filterConn}
|
||||
itemContent={(index, item) => <ConnectionItem value={item} />}
|
||||
/>
|
||||
{filterConn.length > 0 ? (
|
||||
<Virtuoso
|
||||
data={filterConn}
|
||||
itemContent={(index, item) => <ConnectionItem value={item} />}
|
||||
/>
|
||||
) : (
|
||||
<BaseEmpty text="No Connections" />
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
</BasePage>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { atomLogData } from "@/services/states";
|
||||
import BasePage from "@/components/base/base-page";
|
||||
import LogItem from "@/components/log/log-item";
|
||||
import BaseEmpty from "@/components/base/base-empty";
|
||||
|
||||
const LogPage = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -67,7 +68,7 @@ const LogPage = () => {
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
variant="outlined"
|
||||
placeholder="Filter conditions"
|
||||
placeholder={t("Filter conditions")}
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
sx={{ input: { py: 0.65, px: 1.25 } }}
|
||||
@@ -75,12 +76,16 @@ const LogPage = () => {
|
||||
</Box>
|
||||
|
||||
<Box height="calc(100% - 50px)">
|
||||
<Virtuoso
|
||||
initialTopMostItemIndex={999}
|
||||
data={filterLogs}
|
||||
itemContent={(index, item) => <LogItem value={item} />}
|
||||
followOutput={"smooth"}
|
||||
/>
|
||||
{filterLogs.length > 0 ? (
|
||||
<Virtuoso
|
||||
initialTopMostItemIndex={999}
|
||||
data={filterLogs}
|
||||
itemContent={(index, item) => <LogItem value={item} />}
|
||||
followOutput={"smooth"}
|
||||
/>
|
||||
) : (
|
||||
<BaseEmpty text="No Logs" />
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
</BasePage>
|
||||
|
||||
@@ -7,6 +7,7 @@ import monaco from "vite-plugin-monaco-editor";
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
root: "src",
|
||||
server: { port: 3000 },
|
||||
plugins: [
|
||||
svgr(),
|
||||
react(),
|
||||
|
||||
Reference in New Issue
Block a user