mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-12 05:32:09 +08:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c450e830de | ||
|
|
d88fe8dec2 | ||
|
|
dc641bf1f4 | ||
|
|
73b18a28ed | ||
|
|
f84be8df72 |
@@ -4,7 +4,7 @@
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:2": {
|
||||
"version": "26.13.1",
|
||||
"version": "26.13.0",
|
||||
"pnpmVersion": "12.18.0"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/rust:1": {
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"version": "v9.0.0",
|
||||
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
|
||||
},
|
||||
"github/gh-aw-actions/setup@v1.2.2": {
|
||||
"github/gh-aw-actions/setup@v1.0.1": {
|
||||
"repo": "github/gh-aw-actions/setup",
|
||||
"version": "v1.2.2",
|
||||
"sha": "53d0749e19424a918acd82b4d140264ce48f2f14"
|
||||
"version": "v1.0.1",
|
||||
"sha": "221ba4f4319de6b8e809d1a15528b3ae6a818aa5"
|
||||
}
|
||||
},
|
||||
"containers": {
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -262,7 +262,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -395,7 +395,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -479,7 +479,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6.1.1
|
||||
name: Install pnpm
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
@@ -31,9 +31,9 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install gh-aw extension
|
||||
uses: github/gh-aw-actions/setup-cli@53d0749e19424a918acd82b4d140264ce48f2f14 # v1.2.2
|
||||
uses: github/gh-aw-actions/setup-cli@406c7719e31d7fe354224d4bb8fe3d4aeb47da1c # v1.2.1
|
||||
with:
|
||||
version: v1.2.2
|
||||
version: v1.0.1
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
- name: Install frontend dependencies
|
||||
env:
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
+101
-280
File diff suppressed because one or more lines are too long
@@ -33,7 +33,6 @@ max-daily-ai-credits: 5000
|
||||
permissions:
|
||||
issues: read
|
||||
pull-requests: read
|
||||
copilot-requests: none
|
||||
|
||||
pre-agent-steps:
|
||||
- name: Make Copilot quota exhaustion non-blocking
|
||||
|
||||
Generated
+107
-288
File diff suppressed because one or more lines are too long
@@ -30,7 +30,6 @@ permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
copilot-requests: none
|
||||
steps:
|
||||
- name: Load review rules from the base revision
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
if: github.event.action != 'closed'
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
- name: Validate source and publish the approved snapshot
|
||||
id: source
|
||||
env:
|
||||
@@ -231,7 +231,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -330,7 +330,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -461,7 +461,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -542,7 +542,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: false
|
||||
@@ -565,7 +565,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
@@ -593,7 +593,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
@@ -661,7 +661,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
|
||||
+101
-280
File diff suppressed because one or more lines are too long
@@ -28,7 +28,6 @@ permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
copilot-requests: none
|
||||
max-ai-credits: 60
|
||||
max-turns: 12
|
||||
max-daily-ai-credits: 500
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
node-version: '26.13.0'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
nodejs 26.13.1
|
||||
nodejs 26.13.0
|
||||
pnpm 12.18.0
|
||||
rust 1.99.1
|
||||
|
||||
Generated
+4
-4
@@ -159,9 +159,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.9.5"
|
||||
version = "1.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b5d51209f6de83d37a0b33d320acb8da88823e42a94c075140708f032a5"
|
||||
checksum = "db4c94f57a570fce5d381be3289b3f79aef6332d72948fe82ab5772e423ccf92"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
@@ -10126,9 +10126,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "9.5.0"
|
||||
version = "9.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700e6c427aff64acdbf02e85fbfd946c2ee12319d1af013092f3a2178914ff8"
|
||||
checksum = "df3845f734baabdd02de353a20580e30ceccd03897def44a4eb8e9654992e294"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bzip2",
|
||||
|
||||
+1
-3
@@ -24,8 +24,6 @@
|
||||
<details>
|
||||
<summary><strong> 🐞 修复问题 </strong></summary>
|
||||
|
||||
- 修复应用退出时无条件重写 `clash.yaml`、`verge.yaml` 和 `profiles.yaml` 导致文件创建时间丢失的问题
|
||||
|
||||
- 修复检测更新后又发布新版时,应用内更新失败
|
||||
- 修复订阅响应过大导致内存占用过高,限制单次下载为 4 MiB
|
||||
- 修复全局模式默认停在 `DIRECT` 或 `REJECT` 导致流量未经代理,现自动切换到第一个策略组
|
||||
@@ -42,7 +40,6 @@
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
- 新增连接页面按网络协议(TCP/UDP)筛选连接
|
||||
|
||||
</details>
|
||||
|
||||
@@ -51,6 +48,7 @@
|
||||
|
||||
- 优化连接、规则和日志筛选:切换页面后保留过滤条件
|
||||
- 优化全局模式代理列表:仅显示 `GLOBAL` 节点,选中其中的策略组时才显示该组
|
||||
- 优化连接和规则页面的资源占用,减少连接列表更新及排序时的卡顿
|
||||
|
||||
**🍎 macOS**
|
||||
|
||||
|
||||
@@ -74,16 +74,12 @@ impl<T: Clone> Draft<T> {
|
||||
result
|
||||
}
|
||||
|
||||
/// Commits and clears the draft, reporting whether one was staged.
|
||||
/// Commits and clears the draft.
|
||||
#[inline]
|
||||
pub fn apply(&self) -> bool {
|
||||
pub fn apply(&self) {
|
||||
let mut guard = self.inner.data.lock();
|
||||
match guard.1.take() {
|
||||
Some(d) => {
|
||||
guard.0 = d;
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
if let Some(d) = guard.1.take() {
|
||||
guard.0 = d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -67,7 +67,7 @@
|
||||
"dayjs": "1.11.23",
|
||||
"foxact": "^0.3.12",
|
||||
"foxts": "^5.9.1",
|
||||
"i18next": "^27.0.0",
|
||||
"i18next": "^26.4.2",
|
||||
"js-yaml": "^5.4.2",
|
||||
"lodash-es": "^4.18.1",
|
||||
"meta-json-schema": "^1.19.31",
|
||||
@@ -79,7 +79,7 @@
|
||||
"react-dom": "19.3.1",
|
||||
"react-error-boundary": "6.1.6",
|
||||
"react-hook-form": "^7.88.0",
|
||||
"react-i18next": "18.0.1",
|
||||
"react-i18next": "17.1.13",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-router": "^8.4.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
|
||||
Generated
+21
-18
@@ -239,8 +239,8 @@ importers:
|
||||
specifier: ^5.9.1
|
||||
version: 5.9.1
|
||||
i18next:
|
||||
specifier: ^27.0.0
|
||||
version: 27.0.1(typescript@6.0.3)
|
||||
specifier: ^26.4.2
|
||||
version: 26.4.2(typescript@6.0.3)
|
||||
js-yaml:
|
||||
specifier: ^5.4.2
|
||||
version: 5.4.2
|
||||
@@ -275,8 +275,8 @@ importers:
|
||||
specifier: ^7.88.0
|
||||
version: 7.88.0(react@19.3.1)
|
||||
react-i18next:
|
||||
specifier: 18.0.1
|
||||
version: 18.0.1(@types/react@19.3.0)(i18next@27.0.1(typescript@6.0.3))(react@19.3.1)(typescript@6.0.3)
|
||||
specifier: 17.1.13
|
||||
version: 17.1.13(i18next@26.4.2(typescript@6.0.3))(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3)
|
||||
react-markdown:
|
||||
specifier: 10.1.0
|
||||
version: 10.1.0(@types/react@19.3.0)(react@19.3.1)(supports-color@7.2.0)
|
||||
@@ -3689,9 +3689,8 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
i18next@27.0.1:
|
||||
resolution: {integrity: sha512-KaE5ozP8UUeVpOJIwMVbbnpRKpyYQuxd9ZbseJoDzezE3nvLnZZQn20pkzyIJwPI2Uet/Qs3WzPTG812f6F/Eg==}
|
||||
engines: {node: '>=22'}
|
||||
i18next@26.4.2:
|
||||
resolution: {integrity: sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug==}
|
||||
peerDependencies:
|
||||
typescript: ^5 || ^6 || ^7
|
||||
peerDependenciesMeta:
|
||||
@@ -4397,16 +4396,18 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-i18next@18.0.1:
|
||||
resolution: {integrity: sha512-kPWXI0E/lrPj3+/umcKNzYZVnWmEedLiamMzDIHfbs+5ANf6yzvlZsjKou8f7kaGkfMMDLIZBejV+4aSN1CyBw==}
|
||||
engines: {node: '>=22'}
|
||||
react-i18next@17.1.13:
|
||||
resolution: {integrity: sha512-ZxhOSlyKSKPtbUNPLwYMmE3w3bhoc0jaZJZ6bc7wGOT6cLbrbs//6m01HxCLtFdSjHvFpwFwoPhxxzIlHSs9cw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
i18next: '>= 27.0.1'
|
||||
react: '>= 18.0.0'
|
||||
i18next: '>= 26.2.0'
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
typescript: ^5 || ^6 || ^7
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
@@ -8532,7 +8533,7 @@ snapshots:
|
||||
|
||||
husky@9.1.7: {}
|
||||
|
||||
i18next@27.0.1(typescript@6.0.3):
|
||||
i18next@26.4.2(typescript@6.0.3):
|
||||
optionalDependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
@@ -9356,13 +9357,15 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.3.1
|
||||
|
||||
react-i18next@18.0.1(@types/react@19.3.0)(i18next@27.0.1(typescript@6.0.3))(react@19.3.1)(typescript@6.0.3):
|
||||
react-i18next@17.1.13(i18next@26.4.2(typescript@6.0.3))(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
html-parse-stringify: 4.0.1
|
||||
i18next: 27.0.1(typescript@6.0.3)
|
||||
i18next: 26.4.2(typescript@6.0.3)
|
||||
react: 19.3.1
|
||||
use-sync-external-store: 1.7.0(react@19.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.3.0
|
||||
react-dom: 19.3.1(react@19.3.1)
|
||||
typescript: 6.0.3
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
@@ -77,7 +77,7 @@ tauri-plugin-dialog = "2.8.4"
|
||||
tauri-plugin-fs = "2.7.1"
|
||||
tauri-plugin-deep-link = "2.6.3"
|
||||
tauri-plugin-window-state = "2.5.0"
|
||||
zip = "9.5.0"
|
||||
zip = "9.4.2"
|
||||
flate2 = "1.1.11"
|
||||
reqwest_dav = "0.3.3"
|
||||
aes-gcm = { version = "0.11.1" }
|
||||
@@ -97,7 +97,7 @@ async-trait = "0.1.92"
|
||||
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.7", version = "2.7.7", features = [
|
||||
"client",
|
||||
] }
|
||||
arc-swap = "1.9.5"
|
||||
arc-swap = "1.9.4"
|
||||
tokio-rustls = "0.26.6"
|
||||
rustls = { version = "0.23.46", features = ["ring"] }
|
||||
webpki-roots = "1.0.11"
|
||||
|
||||
@@ -280,29 +280,25 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Commits drafts during exit/restart/shutdown so user changes are not lost.
|
||||
/// A config with nothing staged keeps its file, so its timestamps stay put.
|
||||
pub async fn apply_all_and_save_file() {
|
||||
logging!(debug, Type::Config, "save all draft data");
|
||||
let save_clash_task = AsyncHandler::spawn(|| async {
|
||||
let clash = Self::clash().await;
|
||||
if clash.apply() {
|
||||
logging_error!(Type::Config, clash.data_arc().save_config().await);
|
||||
}
|
||||
clash.apply();
|
||||
logging_error!(Type::Config, clash.data_arc().save_config().await);
|
||||
});
|
||||
|
||||
let save_verge_task = AsyncHandler::spawn(|| async {
|
||||
let verge = Self::verge().await;
|
||||
if verge.apply() {
|
||||
logging_error!(Type::Config, verge.data_arc().save_file().await);
|
||||
}
|
||||
verge.apply();
|
||||
logging_error!(Type::Config, verge.data_arc().save_file().await);
|
||||
});
|
||||
|
||||
let save_profiles_task = AsyncHandler::spawn(|| async {
|
||||
let _profile_write = super::profiles::PROFILE_WRITE_LOCK.lock().await;
|
||||
let profiles = Self::profiles().await;
|
||||
if profiles.apply() {
|
||||
logging_error!(Type::Config, profiles.data_arc().save_file().await);
|
||||
}
|
||||
profiles.apply();
|
||||
logging_error!(Type::Config, profiles.data_arc().save_file().await);
|
||||
});
|
||||
|
||||
let _ = tokio::join!(save_clash_task, save_verge_task, save_profiles_task);
|
||||
|
||||
@@ -8,7 +8,7 @@ let timerId: number | null = null
|
||||
const listeners = new Set<RelativeTimeListener>()
|
||||
|
||||
const startTimer = () => {
|
||||
if (timerId !== null) return
|
||||
if (timerId !== null || document.hidden || listeners.size === 0) return
|
||||
|
||||
currentTime = Date.now()
|
||||
timerId = window.setInterval(() => {
|
||||
@@ -18,19 +18,34 @@ const startTimer = () => {
|
||||
}
|
||||
|
||||
const stopTimer = () => {
|
||||
if (listeners.size > 0 || timerId === null) return
|
||||
if (timerId === null) return
|
||||
|
||||
window.clearInterval(timerId)
|
||||
timerId = null
|
||||
}
|
||||
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.hidden) {
|
||||
stopTimer()
|
||||
} else {
|
||||
startTimer()
|
||||
listeners.forEach((listener) => listener())
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeRelativeTime = (listener: RelativeTimeListener) => {
|
||||
listeners.add(listener)
|
||||
if (listeners.size === 1) {
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
startTimer()
|
||||
|
||||
return () => {
|
||||
listeners.delete(listener)
|
||||
stopTimer()
|
||||
if (listeners.size === 0) {
|
||||
stopTimer()
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,16 @@ const compareConnectionCellValue = (
|
||||
right: IConnectionsItem,
|
||||
getSnapshot: (row: IConnectionsItem) => TableRowSnapshot,
|
||||
) => {
|
||||
switch (field) {
|
||||
case 'download':
|
||||
return (left.download ?? 0) - (right.download ?? 0)
|
||||
case 'upload':
|
||||
return (left.upload ?? 0) - (right.upload ?? 0)
|
||||
case 'dlSpeed':
|
||||
return (left.curDownload ?? 0) - (right.curDownload ?? 0)
|
||||
case 'ulSpeed':
|
||||
return (left.curUpload ?? 0) - (right.curUpload ?? 0)
|
||||
}
|
||||
const leftValue = getConnectionCellValue(field, getSnapshot(left))
|
||||
const rightValue = getConnectionCellValue(field, getSnapshot(right))
|
||||
|
||||
@@ -491,6 +501,8 @@ export const ConnectionTable = (props: Props) => {
|
||||
const [sorting, setSorting] = useState<SortingState | null>(null)
|
||||
const [viewport, setViewport] = useState({ scrollTop: 0, height: 0 })
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
const resizeCleanupRef = useRef<(() => void) | null>(null)
|
||||
useEffect(() => () => resizeCleanupRef.current?.(), [])
|
||||
const rowSnapshotCacheRef = useRef(new Map<string, TableRowSnapshot>())
|
||||
const getRowSnapshot = useCallback((row: IConnectionsItem) => {
|
||||
const cache = rowSnapshotCacheRef.current
|
||||
@@ -664,6 +676,7 @@ export const ConnectionTable = (props: Props) => {
|
||||
minWidth: number,
|
||||
maxWidth: number | undefined,
|
||||
) => {
|
||||
resizeCleanupRef.current?.()
|
||||
const handleMove = (clientX: number) => {
|
||||
const nextWidth = Math.max(
|
||||
minWidth,
|
||||
@@ -686,8 +699,10 @@ export const ConnectionTable = (props: Props) => {
|
||||
window.removeEventListener('touchmove', handleTouchMove)
|
||||
window.removeEventListener('touchend', cleanup)
|
||||
window.removeEventListener('touchcancel', cleanup)
|
||||
resizeCleanupRef.current = null
|
||||
}
|
||||
|
||||
resizeCleanupRef.current = cleanup
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('mouseup', cleanup)
|
||||
window.addEventListener('touchmove', handleTouchMove, { passive: true })
|
||||
|
||||
@@ -16,7 +16,8 @@ const COLOR = [
|
||||
]
|
||||
|
||||
interface Props {
|
||||
value: Rule & { lineNo: number }
|
||||
value: Rule
|
||||
lineNo: number
|
||||
}
|
||||
|
||||
const parseColor = (text: string) => {
|
||||
@@ -31,7 +32,7 @@ const parseColor = (text: string) => {
|
||||
}
|
||||
|
||||
const RuleItem = (props: Props) => {
|
||||
const { value } = props
|
||||
const { value, lineNo } = props
|
||||
|
||||
return (
|
||||
<Item sx={{ borderBottom: '1px solid var(--divider-color)' }}>
|
||||
@@ -40,7 +41,7 @@ const RuleItem = (props: Props) => {
|
||||
variant="body2"
|
||||
sx={{ lineHeight: 2, minWidth: 30, mr: 2.25, textAlign: 'center' }}
|
||||
>
|
||||
{value.lineNo}
|
||||
{lineNo}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ userSelect: 'text' }}>
|
||||
|
||||
@@ -150,25 +150,49 @@ const mergeConnectionSnapshot = (
|
||||
const nextConnections = payload.connections ?? []
|
||||
const previousActive = previous.activeConnections ?? []
|
||||
const previousClosed = previous.closedConnections ?? []
|
||||
const previousActiveById = new Map<string, IConnectionsItem>()
|
||||
const sameOrder =
|
||||
nextConnections.length === previousActive.length &&
|
||||
nextConnections.every(
|
||||
(connection, i) => connection.id === previousActive[i].id,
|
||||
)
|
||||
let previousActiveById: Map<string, IConnectionsItem> | undefined
|
||||
|
||||
for (let i = 0; i < previousActive.length; i++) {
|
||||
const previousConnection = previousActive[i]
|
||||
previousActiveById.set(previousConnection.id, previousConnection)
|
||||
if (!sameOrder) {
|
||||
previousActiveById = new Map()
|
||||
for (let i = 0; i < previousActive.length; i++) {
|
||||
const previousConnection = previousActive[i]
|
||||
previousActiveById.set(previousConnection.id, previousConnection)
|
||||
}
|
||||
}
|
||||
|
||||
const activeConnections: IConnectionsItem[] = []
|
||||
let activeConnections = sameOrder ? previousActive : []
|
||||
for (let i = 0; i < nextConnections.length; i++) {
|
||||
const connection = nextConnections[i]
|
||||
const previousConnection = previousActiveById.get(connection.id)
|
||||
if (previousConnection) previousActiveById.delete(connection.id)
|
||||
activeConnections.push(normalizeConnection(connection, previousConnection))
|
||||
const previousConnection = sameOrder
|
||||
? previousActive[i]
|
||||
: previousActiveById?.get(connection.id)
|
||||
previousActiveById?.delete(connection.id)
|
||||
const normalized = normalizeConnection(connection, previousConnection)
|
||||
if (activeConnections[i] === normalized) continue
|
||||
if (activeConnections === previousActive) {
|
||||
activeConnections = previousActive.slice()
|
||||
}
|
||||
activeConnections[i] = normalized
|
||||
}
|
||||
|
||||
if (previousActiveById.size === 0) {
|
||||
const uploadTotal = payload.uploadTotal ?? 0
|
||||
const downloadTotal = payload.downloadTotal ?? 0
|
||||
if (!previousActiveById?.size) {
|
||||
if (
|
||||
activeConnections === previousActive &&
|
||||
uploadTotal === previous.uploadTotal &&
|
||||
downloadTotal === previous.downloadTotal
|
||||
) {
|
||||
return previous
|
||||
}
|
||||
return {
|
||||
uploadTotal: payload.uploadTotal ?? 0,
|
||||
downloadTotal: payload.downloadTotal ?? 0,
|
||||
uploadTotal,
|
||||
downloadTotal,
|
||||
activeConnections,
|
||||
closedConnections: previousClosed,
|
||||
}
|
||||
@@ -198,8 +222,8 @@ const mergeConnectionSnapshot = (
|
||||
}
|
||||
|
||||
return {
|
||||
uploadTotal: payload.uploadTotal ?? 0,
|
||||
downloadTotal: payload.downloadTotal ?? 0,
|
||||
uploadTotal,
|
||||
downloadTotal,
|
||||
activeConnections,
|
||||
closedConnections,
|
||||
}
|
||||
@@ -227,7 +251,9 @@ const flushPendingMessage = () => {
|
||||
|
||||
lastFlushAt = Date.now()
|
||||
|
||||
connectionData = mergeConnectionSnapshot(payload, connectionData)
|
||||
const nextData = mergeConnectionSnapshot(payload, connectionData)
|
||||
if (nextData === connectionData) return
|
||||
connectionData = nextData
|
||||
notifyConnectionListeners()
|
||||
}
|
||||
|
||||
|
||||
@@ -71,10 +71,6 @@ const ORDER_OPTIONS = [
|
||||
|
||||
type OrderKey = (typeof ORDER_OPTIONS)[number]['id']
|
||||
|
||||
type NetworkFilter = 'all' | 'tcp' | 'udp'
|
||||
|
||||
let savedNetworkFilter: NetworkFilter = 'all'
|
||||
|
||||
const orderFunctionMap = ORDER_OPTIONS.reduce<Record<OrderKey, OrderFunc>>(
|
||||
(acc, option) => {
|
||||
acc[option.id] = option.fn
|
||||
@@ -90,9 +86,6 @@ const ConnectionsPage = () => {
|
||||
const { searchState, match, onSearch } = usePageSearch('connections')
|
||||
const hasSearch = searchState.text.length > 0
|
||||
const [curOrderOpt, setCurOrderOpt] = useState<OrderKey>('default')
|
||||
const [networkFilter, setNetworkFilter] =
|
||||
useState<NetworkFilter>(savedNetworkFilter)
|
||||
const hasFilter = networkFilter !== 'all'
|
||||
const [connectionsType, setConnectionsType] = useState<'active' | 'closed'>(
|
||||
'active',
|
||||
)
|
||||
@@ -119,30 +112,18 @@ const ConnectionsPage = () => {
|
||||
const filterConn = useMemo(() => {
|
||||
const orderFunc = orderFunctionMap[curOrderOpt]
|
||||
|
||||
if (isTableLayout && !hasSearch && !hasFilter) return selectedConnections
|
||||
if (!hasSearch && !hasFilter) return orderFunc([...selectedConnections])
|
||||
if (isTableLayout && !hasSearch) return selectedConnections
|
||||
if (!hasSearch) return orderFunc([...selectedConnections])
|
||||
|
||||
const matchConns = selectedConnections.filter((conn) => {
|
||||
const { host, destinationIP, process, network } = conn.metadata
|
||||
if (networkFilter !== 'all' && network !== networkFilter) return false
|
||||
const { host, destinationIP, process } = conn.metadata
|
||||
return (
|
||||
!hasSearch ||
|
||||
match(host || '') ||
|
||||
match(destinationIP || '') ||
|
||||
match(process || '')
|
||||
match(host || '') || match(destinationIP || '') || match(process || '')
|
||||
)
|
||||
})
|
||||
|
||||
return orderFunc ? orderFunc(matchConns) : matchConns
|
||||
}, [
|
||||
selectedConnections,
|
||||
isTableLayout,
|
||||
hasSearch,
|
||||
hasFilter,
|
||||
networkFilter,
|
||||
match,
|
||||
curOrderOpt,
|
||||
])
|
||||
}, [selectedConnections, isTableLayout, hasSearch, match, curOrderOpt])
|
||||
|
||||
const displayRows = useConnectionRowViews(
|
||||
isTableLayout ? EMPTY_CONNECTIONS : filterConn,
|
||||
@@ -274,24 +255,6 @@ const ConnectionsPage = () => {
|
||||
))}
|
||||
</BaseStyledSelect>
|
||||
)}
|
||||
<BaseStyledSelect
|
||||
style={{ width: 80 }}
|
||||
value={networkFilter}
|
||||
onChange={(e) => {
|
||||
savedNetworkFilter = e.target.value as NetworkFilter
|
||||
setNetworkFilter(savedNetworkFilter)
|
||||
}}
|
||||
>
|
||||
<MenuItem value="all">
|
||||
<span style={{ fontSize: 14 }}>All</span>
|
||||
</MenuItem>
|
||||
<MenuItem value="tcp">
|
||||
<span style={{ fontSize: 14 }}>TCP</span>
|
||||
</MenuItem>
|
||||
<MenuItem value="udp">
|
||||
<span style={{ fontSize: 14 }}>UDP</span>
|
||||
</MenuItem>
|
||||
</BaseStyledSelect>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
|
||||
+11
-8
@@ -34,13 +34,11 @@ const RulesPage = () => {
|
||||
}, [refreshRules, refreshRuleProviders, pageVisible])
|
||||
|
||||
const filteredRules = useMemo(() => {
|
||||
const rulesWithLineNo = rules.map((item, index) => ({
|
||||
...item,
|
||||
// UI-only derived data; keep app context/SWR data immutable
|
||||
lineNo: index + 1,
|
||||
}))
|
||||
|
||||
return rulesWithLineNo.filter((item) => match(item.payload ?? ''))
|
||||
const indices: number[] = []
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
if (match(rules[i].payload ?? '')) indices.push(i)
|
||||
}
|
||||
return indices
|
||||
}, [rules, match])
|
||||
|
||||
const handleScroll = useCallback((e: Event) => {
|
||||
@@ -90,7 +88,12 @@ const RulesPage = () => {
|
||||
ref={virtuosoRef}
|
||||
count={filteredRules.length}
|
||||
estimateSize={40}
|
||||
renderItem={(i) => <RuleItem value={filteredRules[i]} />}
|
||||
renderItem={(i) => (
|
||||
<RuleItem
|
||||
value={rules[filteredRules[i]]}
|
||||
lineNo={filteredRules[i] + 1}
|
||||
/>
|
||||
)}
|
||||
style={{ flex: 1 }}
|
||||
onScroll={handleScroll}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user