diff --git a/.github/update.log b/.github/update.log
index 062fdb2b38..0dae56d533 100644
--- a/.github/update.log
+++ b/.github/update.log
@@ -1133,3 +1133,4 @@ Update On Tue Sep 23 20:37:30 CEST 2025
Update On Wed Sep 24 20:34:28 CEST 2025
Update On Thu Sep 25 20:42:08 CEST 2025
Update On Fri Sep 26 20:35:20 CEST 2025
+Update On Sat Sep 27 20:33:29 CEST 2025
diff --git a/clash-nyanpasu/README.md b/clash-nyanpasu/README.md
index 3f85e72233..c8cc1b092c 100644
--- a/clash-nyanpasu/README.md
+++ b/clash-nyanpasu/README.md
@@ -15,6 +15,7 @@
+
## Features
diff --git a/clash-nyanpasu/frontend/interface/package.json b/clash-nyanpasu/frontend/interface/package.json
index 8f263337b5..0059690454 100644
--- a/clash-nyanpasu/frontend/interface/package.json
+++ b/clash-nyanpasu/frontend/interface/package.json
@@ -22,6 +22,6 @@
},
"devDependencies": {
"@types/lodash-es": "4.17.12",
- "@types/react": "19.1.12"
+ "@types/react": "19.1.14"
}
}
diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json
index 2618646822..37ff152331 100644
--- a/clash-nyanpasu/frontend/nyanpasu/package.json
+++ b/clash-nyanpasu/frontend/nyanpasu/package.json
@@ -33,7 +33,7 @@
"dayjs": "1.11.18",
"framer-motion": "12.23.22",
"i18next": "25.5.2",
- "jotai": "2.14.0",
+ "jotai": "2.15.0",
"json-schema": "0.4.0",
"material-react-table": "3.2.1",
"monaco-editor": "0.52.2",
@@ -49,7 +49,7 @@
"react-use": "17.6.0",
"rxjs": "7.8.2",
"swr": "2.3.6",
- "virtua": "0.43.4",
+ "virtua": "0.43.5",
"vite-bundle-visualizer": "1.2.1"
},
"devDependencies": {
@@ -60,7 +60,7 @@
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.90.2",
"@tanstack/react-router": "1.132.7",
- "@tanstack/react-router-devtools": "1.132.7",
+ "@tanstack/react-router-devtools": "1.132.11",
"@tanstack/router-plugin": "1.132.7",
"@tauri-apps/plugin-clipboard-manager": "2.3.0",
"@tauri-apps/plugin-dialog": "2.4.0",
@@ -70,7 +70,7 @@
"@tauri-apps/plugin-process": "2.3.0",
"@tauri-apps/plugin-shell": "2.3.1",
"@tauri-apps/plugin-updater": "2.9.0",
- "@types/react": "19.1.12",
+ "@types/react": "19.1.14",
"@types/react-dom": "19.1.9",
"@types/validator": "13.15.3",
"@vitejs/plugin-legacy": "7.2.1",
diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json
index e2dbebe5bb..6daf1be2e5 100644
--- a/clash-nyanpasu/frontend/ui/package.json
+++ b/clash-nyanpasu/frontend/ui/package.json
@@ -19,7 +19,7 @@
"@radix-ui/react-scroll-area": "1.2.10",
"@tauri-apps/api": "2.8.0",
"@types/d3": "7.4.3",
- "@types/react": "19.1.12",
+ "@types/react": "19.1.14",
"@vitejs/plugin-react": "5.0.3",
"ahooks": "3.9.5",
"d3": "7.9.0",
diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json
index 6a1cd02a39..697c6fcc7e 100644
--- a/clash-nyanpasu/manifest/version.json
+++ b/clash-nyanpasu/manifest/version.json
@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.14",
- "mihomo_alpha": "alpha-fdc46f0",
+ "mihomo_alpha": "alpha-f45c6f5",
"clash_rs": "v0.9.0",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.9.0-alpha+sha.2784d7a"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
- "updated_at": "2025-09-24T22:21:01.034Z"
+ "updated_at": "2025-09-26T22:21:10.718Z"
}
diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json
index 9aec00d565..e4743f3eea 100644
--- a/clash-nyanpasu/package.json
+++ b/clash-nyanpasu/package.json
@@ -66,7 +66,7 @@
"@tauri-apps/cli": "2.8.4",
"@types/fs-extra": "11.0.4",
"@types/lodash-es": "4.17.12",
- "@types/node": "24.3.1",
+ "@types/node": "24.5.2",
"@typescript-eslint/eslint-plugin": "8.44.1",
"@typescript-eslint/parser": "8.44.1",
"autoprefixer": "10.4.21",
@@ -106,7 +106,7 @@
"stylelint-order": "7.0.0",
"stylelint-scss": "6.12.1",
"tailwindcss": "4.1.13",
- "tsx": "4.20.5",
+ "tsx": "4.20.6",
"typescript": "5.9.2",
"typescript-eslint": "8.44.1"
},
diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml
index c14a41c6a0..7dddadd7d2 100644
--- a/clash-nyanpasu/pnpm-lock.yaml
+++ b/clash-nyanpasu/pnpm-lock.yaml
@@ -24,7 +24,7 @@ importers:
devDependencies:
'@commitlint/cli':
specifier: 19.8.1
- version: 19.8.1(@types/node@24.3.1)(typescript@5.9.2)
+ version: 19.8.1(@types/node@24.5.2)(typescript@5.9.2)
'@commitlint/config-conventional':
specifier: 19.8.1
version: 19.8.1
@@ -47,8 +47,8 @@ importers:
specifier: 4.17.12
version: 4.17.12
'@types/node':
- specifier: 24.3.1
- version: 24.3.1
+ specifier: 24.5.2
+ version: 24.5.2
'@typescript-eslint/eslint-plugin':
specifier: 8.44.1
version: 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
@@ -105,7 +105,7 @@ importers:
version: 16.4.0
knip:
specifier: 5.64.1
- version: 5.64.1(@types/node@24.3.1)(typescript@5.9.2)
+ version: 5.64.1(@types/node@24.5.2)(typescript@5.9.2)
lint-staged:
specifier: 16.2.1
version: 16.2.1
@@ -167,8 +167,8 @@ importers:
specifier: 4.1.13
version: 4.1.13
tsx:
- specifier: 4.20.5
- version: 4.20.5
+ specifier: 4.20.6
+ version: 4.20.6
typescript:
specifier: 5.9.2
version: 5.9.2
@@ -207,8 +207,8 @@ importers:
specifier: 4.17.12
version: 4.17.12
'@types/react':
- specifier: 19.1.12
- version: 19.1.12
+ specifier: 19.1.14
+ version: 19.1.14
frontend/nyanpasu:
dependencies:
@@ -223,7 +223,7 @@ importers:
version: 3.2.2(react@19.1.1)
'@emotion/styled':
specifier: 11.14.1
- version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
'@juggle/resize-observer':
specifier: 3.4.0
version: 3.4.0
@@ -232,16 +232,16 @@ importers:
version: 0.3.0
'@mui/icons-material':
specifier: 7.3.2
- version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
+ version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
'@mui/lab':
specifier: 7.0.0-beta.17
- version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material':
specifier: 7.3.2
- version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/x-date-pickers':
specifier: 8.12.0
- version: 8.12.0(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 8.12.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@nyanpasu/interface':
specifier: workspace:^
version: link:../interface
@@ -282,20 +282,20 @@ importers:
specifier: 25.5.2
version: 25.5.2(typescript@5.9.2)
jotai:
- specifier: 2.14.0
- version: 2.14.0(@babel/core@7.28.4)(@babel/template@7.27.2)(@types/react@19.1.12)(react@19.1.1)
+ specifier: 2.15.0
+ version: 2.15.0(@babel/core@7.28.4)(@babel/template@7.27.2)(@types/react@19.1.14)(react@19.1.1)
json-schema:
specifier: 0.4.0
version: 0.4.0
material-react-table:
specifier: npm:@greenhat616/material-react-table@4.0.0
- version: '@greenhat616/material-react-table@4.0.0(306c2a7e09663f4b4ca18aa376f632d7)'
+ version: '@greenhat616/material-react-table@4.0.0(1271a7024e323b47c86660a86391d8f0)'
monaco-editor:
specifier: 0.52.2
version: 0.52.2
mui-color-input:
specifier: 7.0.0
- version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react:
specifier: 19.1.1
version: 19.1.1
@@ -310,13 +310,13 @@ importers:
version: 1.6.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-hook-form-mui:
specifier: 8.0.0
- version: 8.0.0(a67848f6b3a393292e2dcb1a943d7b01)
+ version: 8.0.0(5034abb0a696b1128593edad6cde9ffd)
react-i18next:
specifier: 15.7.4
version: 15.7.4(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)
react-markdown:
specifier: 10.1.0
- version: 10.1.0(@types/react@19.1.12)(react@19.1.1)
+ version: 10.1.0(@types/react@19.1.14)(react@19.1.1)
react-split-grid:
specifier: 1.0.4
version: 1.0.4(react@19.1.1)
@@ -330,8 +330,8 @@ importers:
specifier: 2.3.6
version: 2.3.6(react@19.1.1)
virtua:
- specifier: 0.43.4
- version: 0.43.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)
+ specifier: 0.43.5
+ version: 0.43.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)
vite-bundle-visualizer:
specifier: 1.2.1
version: 1.2.1(rollup@4.46.2)
@@ -344,7 +344,7 @@ importers:
version: 11.13.5
'@emotion/react':
specifier: 11.14.0
- version: 11.14.0(@types/react@19.1.12)(react@19.1.1)
+ version: 11.14.0(@types/react@19.1.14)(react@19.1.1)
'@iconify/json':
specifier: 2.2.388
version: 2.2.388
@@ -358,11 +358,11 @@ importers:
specifier: 1.132.7
version: 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/react-router-devtools':
- specifier: 1.132.7
- version: 1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.132.7)(@types/node@24.3.1)(csstype@3.1.3)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass-embedded@1.93.2)(sass@1.93.2)(solid-js@1.9.5)(stylus@0.62.0)(terser@5.36.0)(tiny-invariant@1.3.3)(tsx@4.20.5)(yaml@2.8.1)
+ specifier: 1.132.11
+ version: 1.132.11(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.132.7)(@types/node@24.5.2)(csstype@3.1.3)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass-embedded@1.93.2)(sass@1.93.2)(solid-js@1.9.5)(stylus@0.62.0)(terser@5.36.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)
'@tanstack/router-plugin':
specifier: 1.132.7
- version: 1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
'@tauri-apps/plugin-clipboard-manager':
specifier: 2.3.0
version: 2.3.0
@@ -388,23 +388,23 @@ importers:
specifier: 2.9.0
version: 2.9.0
'@types/react':
- specifier: 19.1.12
- version: 19.1.12
+ specifier: 19.1.14
+ version: 19.1.14
'@types/react-dom':
specifier: 19.1.9
- version: 19.1.9(@types/react@19.1.12)
+ version: 19.1.9(@types/react@19.1.14)
'@types/validator':
specifier: 13.15.3
version: 13.15.3
'@vitejs/plugin-legacy':
specifier: 7.2.1
- version: 7.2.1(terser@5.36.0)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 7.2.1(terser@5.36.0)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
'@vitejs/plugin-react':
specifier: 5.0.3
- version: 5.0.3(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 5.0.3(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
'@vitejs/plugin-react-swc':
specifier: 4.1.0
- version: 4.1.0(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 4.1.0(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
change-case:
specifier: 5.4.4
version: 5.4.4
@@ -443,19 +443,19 @@ importers:
version: 13.15.15
vite:
specifier: 7.1.7
- version: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ version: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
vite-plugin-html:
specifier: 3.2.2
- version: 3.2.2(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 3.2.2(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
vite-plugin-sass-dts:
specifier: 1.3.31
- version: 1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.93.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.93.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
vite-plugin-svgr:
specifier: 4.5.0
- version: 4.5.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 4.5.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
vite-tsconfig-paths:
specifier: 5.1.4
- version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
zod:
specifier: 4.1.11
version: 4.1.11
@@ -467,19 +467,19 @@ importers:
version: 0.3.0
'@mui/icons-material':
specifier: 7.3.2
- version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
+ version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
'@mui/lab':
specifier: 7.0.0-beta.17
- version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material':
specifier: 7.3.2
- version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-portal':
specifier: 1.1.9
- version: 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-scroll-area':
specifier: 1.2.10
- version: 1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 1.2.10(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tauri-apps/api':
specifier: 2.8.0
version: 2.8.0
@@ -487,11 +487,11 @@ importers:
specifier: 7.4.3
version: 7.4.3
'@types/react':
- specifier: 19.1.12
- version: 19.1.12
+ specifier: 19.1.14
+ version: 19.1.14
'@vitejs/plugin-react':
specifier: 5.0.3
- version: 5.0.3(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 5.0.3(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
ahooks:
specifier: 3.9.5
version: 3.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -521,14 +521,14 @@ importers:
version: 4.1.13
vite:
specifier: 7.1.7
- version: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ version: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
vite-tsconfig-paths:
specifier: 5.1.4
- version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
devDependencies:
'@emotion/react':
specifier: 11.14.0
- version: 11.14.0(@types/react@19.1.12)(react@19.1.1)
+ version: 11.14.0(@types/react@19.1.14)(react@19.1.1)
'@types/d3-interpolate-path':
specifier: 2.0.3
version: 2.0.3
@@ -549,7 +549,7 @@ importers:
version: 5.2.0(typescript@5.9.2)
vite-plugin-dts:
specifier: 4.5.4
- version: 4.5.4(@types/node@24.3.1)(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ version: 4.5.4(@types/node@24.5.2)(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))
scripts:
dependencies:
@@ -3042,8 +3042,8 @@ packages:
peerDependencies:
react: ^18 || ^19
- '@tanstack/react-router-devtools@1.132.7':
- resolution: {integrity: sha512-Sx+Vx96seR/trFL5SXUEh8LxfqVkYGFQsTm2jAilMtAZKD0Mt7gZ2j4o14kpazevxUv7nUI7c+vdINGipyfEcA==}
+ '@tanstack/react-router-devtools@1.132.11':
+ resolution: {integrity: sha512-5UxJjyHxDBX2Sh8E5oN+YagfHiQ0bnhDx6Gguff2sCcMw/MgxaAcQ51EoQcAOwt36KAvh9CIp6VJiM9X6uhVRw==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/react-router': ^1.132.7
@@ -3080,8 +3080,8 @@ packages:
resolution: {integrity: sha512-HgarEo427V1rlpQtA1jIoK4/S/JYgM7/mU3TKO2d7/tyx0FRE9LP8hvUmlxz/Zi71lD6yaICHoCSVcOh0dGLbA==}
engines: {node: '>=12'}
- '@tanstack/router-devtools-core@1.132.7':
- resolution: {integrity: sha512-pSu479zcw+e/cAv8PLQ7oIAnqQ5G5EjibYrj+fY6ShMZrRPBw09AudchrCbFBIF0YYAOY1narav7xHeqEJZpIA==}
+ '@tanstack/router-devtools-core@1.132.11':
+ resolution: {integrity: sha512-77cMrmVzLXVuSxTr6HTQSTVqzRtY4ITyDk4pZs2QI84dHujt0aMjHj9WnvqBe/0QW0nqgJBi4e8PmsVNXgUv7Q==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/router-core': ^1.132.7
@@ -3441,8 +3441,8 @@ packages:
'@types/node@16.18.108':
resolution: {integrity: sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==}
- '@types/node@24.3.1':
- resolution: {integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==}
+ '@types/node@24.5.2':
+ resolution: {integrity: sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -3466,8 +3466,8 @@ packages:
peerDependencies:
'@types/react': '*'
- '@types/react@19.1.12':
- resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==}
+ '@types/react@19.1.14':
+ resolution: {integrity: sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==}
'@types/responselike@1.0.3':
resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
@@ -5979,8 +5979,8 @@ packages:
jju@1.4.0:
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
- jotai@2.14.0:
- resolution: {integrity: sha512-JQkNkTnqjk1BlSUjHfXi+pGG/573bVN104gp6CymhrWDseZGDReTNniWrLhJ+zXbM6pH+82+UNJ2vwYQUkQMWQ==}
+ jotai@2.15.0:
+ resolution: {integrity: sha512-nbp/6jN2Ftxgw0VwoVnOg0m5qYM1rVcfvij+MZx99Z5IK13eGve9FJoCwGv+17JvVthTjhSmNtT5e1coJnr6aw==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@babel/core': '>=7.0.0'
@@ -8087,8 +8087,8 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
- tsx@4.20.5:
- resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==}
+ tsx@4.20.6:
+ resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}
engines: {node: '>=18.0.0'}
hasBin: true
@@ -8177,8 +8177,8 @@ packages:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
- undici-types@7.10.0:
- resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
+ undici-types@7.12.0:
+ resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==}
undici@5.29.0:
resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
@@ -8358,8 +8358,8 @@ packages:
vfile@6.0.1:
resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
- virtua@0.43.4:
- resolution: {integrity: sha512-zS9A2EC3KY9hRjG5NM7t0gPGqkcdOc7HWhuc9KDvKq3yz10sRup5L09axFyWHePr1ppWljpAHtr0BCCsM6tS5Q==}
+ virtua@0.43.5:
+ resolution: {integrity: sha512-Isu5UvvE+hs+1iTUfBgS4FakbAqgG8ALZP1/kLSyEASvHI4rANpWi5g+1BF8sUXVcWHStBniGVLZv1iX2i/qPw==}
peerDependencies:
react: '>=16.14.0'
react-dom: '>=16.14.0'
@@ -9729,11 +9729,11 @@ snapshots:
'@bufbuild/protobuf@2.5.2': {}
- '@commitlint/cli@19.8.1(@types/node@24.3.1)(typescript@5.9.2)':
+ '@commitlint/cli@19.8.1(@types/node@24.5.2)(typescript@5.9.2)':
dependencies:
'@commitlint/format': 19.8.1
'@commitlint/lint': 19.8.1
- '@commitlint/load': 19.8.1(@types/node@24.3.1)(typescript@5.9.2)
+ '@commitlint/load': 19.8.1(@types/node@24.5.2)(typescript@5.9.2)
'@commitlint/read': 19.8.1
'@commitlint/types': 19.8.1
tinyexec: 1.0.1
@@ -9780,7 +9780,7 @@ snapshots:
'@commitlint/rules': 19.8.1
'@commitlint/types': 19.8.1
- '@commitlint/load@19.8.1(@types/node@24.3.1)(typescript@5.9.2)':
+ '@commitlint/load@19.8.1(@types/node@24.5.2)(typescript@5.9.2)':
dependencies:
'@commitlint/config-validator': 19.8.1
'@commitlint/execute-rule': 19.8.1
@@ -9788,7 +9788,7 @@ snapshots:
'@commitlint/types': 19.8.1
chalk: 5.4.1
cosmiconfig: 9.0.0(typescript@5.9.2)
- cosmiconfig-typescript-loader: 6.1.0(@types/node@24.3.1)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2)
+ cosmiconfig-typescript-loader: 6.1.0(@types/node@24.5.2)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@@ -9965,7 +9965,7 @@ snapshots:
'@emotion/memoize@0.9.0': {}
- '@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1)':
+ '@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.26.0
'@emotion/babel-plugin': 11.13.5
@@ -9977,7 +9977,7 @@ snapshots:
hoist-non-react-statics: 3.3.2
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
transitivePeerDependencies:
- supports-color
@@ -9991,18 +9991,18 @@ snapshots:
'@emotion/sheet@1.4.0': {}
- '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)':
+ '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.27.6
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.3.0
- '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1)
'@emotion/utils': 1.4.2
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
transitivePeerDependencies:
- supports-color
@@ -10154,13 +10154,13 @@ snapshots:
'@fastify/busboy@2.1.1': {}
- '@greenhat616/material-react-table@4.0.0(306c2a7e09663f4b4ca18aa376f632d7)':
+ '@greenhat616/material-react-table@4.0.0(1271a7024e323b47c86660a86391d8f0)':
dependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@mui/icons-material': 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@mui/x-date-pickers': 8.12.0(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/icons-material': 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/x-date-pickers': 8.12.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/match-sorter-utils': 8.19.4
'@tanstack/react-table': 8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/react-virtual': 3.13.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -10254,23 +10254,23 @@ snapshots:
'@material/material-color-utilities@0.3.0': {}
- '@microsoft/api-extractor-model@7.30.3(@types/node@24.3.1)':
+ '@microsoft/api-extractor-model@7.30.3(@types/node@24.5.2)':
dependencies:
'@microsoft/tsdoc': 0.15.1
'@microsoft/tsdoc-config': 0.17.1
- '@rushstack/node-core-library': 5.11.0(@types/node@24.3.1)
+ '@rushstack/node-core-library': 5.11.0(@types/node@24.5.2)
transitivePeerDependencies:
- '@types/node'
- '@microsoft/api-extractor@7.51.0(@types/node@24.3.1)':
+ '@microsoft/api-extractor@7.51.0(@types/node@24.5.2)':
dependencies:
- '@microsoft/api-extractor-model': 7.30.3(@types/node@24.3.1)
+ '@microsoft/api-extractor-model': 7.30.3(@types/node@24.5.2)
'@microsoft/tsdoc': 0.15.1
'@microsoft/tsdoc-config': 0.17.1
- '@rushstack/node-core-library': 5.11.0(@types/node@24.3.1)
+ '@rushstack/node-core-library': 5.11.0(@types/node@24.5.2)
'@rushstack/rig-package': 0.5.3
- '@rushstack/terminal': 0.15.0(@types/node@24.3.1)
- '@rushstack/ts-command-line': 4.23.5(@types/node@24.3.1)
+ '@rushstack/terminal': 0.15.0(@types/node@24.5.2)
+ '@rushstack/ts-command-line': 4.23.5(@types/node@24.5.2)
lodash: 4.17.21
minimatch: 3.0.8
resolve: 1.22.8
@@ -10302,39 +10302,39 @@ snapshots:
'@mui/core-downloads-tracker@7.3.2': {}
- '@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)':
+ '@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.12)
- '@mui/utils': 7.3.2(@types/react@19.1.12)(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.14)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@types/react': 19.1.12
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@types/react': 19.1.14
- '@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/core-downloads-tracker': 7.3.2
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.12)
- '@mui/utils': 7.3.2(@types/react@19.1.12)(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.14)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
'@popperjs/core': 2.11.8
- '@types/react-transition-group': 4.4.12(@types/react@19.1.12)
+ '@types/react-transition-group': 4.4.12(@types/react@19.1.14)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
@@ -10343,20 +10343,20 @@ snapshots:
react-is: 19.1.1
react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@types/react': 19.1.12
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@types/react': 19.1.14
- '@mui/private-theming@7.3.2(@types/react@19.1.12)(react@19.1.1)':
+ '@mui/private-theming@7.3.2(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
- '@mui/utils': 7.3.2(@types/react@19.1.12)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@mui/styled-engine@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)':
+ '@mui/styled-engine@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
'@emotion/cache': 11.14.0
@@ -10366,67 +10366,67 @@ snapshots:
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
- '@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)':
+ '@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
- '@mui/private-theming': 7.3.2(@types/react@19.1.12)(react@19.1.1)
- '@mui/styled-engine': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.12)
- '@mui/utils': 7.3.2(@types/react@19.1.12)(react@19.1.1)
+ '@mui/private-theming': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/styled-engine': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.14)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@types/react': 19.1.12
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@types/react': 19.1.14
- '@mui/types@7.4.6(@types/react@19.1.12)':
+ '@mui/types@7.4.6(@types/react@19.1.14)':
dependencies:
'@babel/runtime': 7.28.3
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@mui/utils@7.3.2(@types/react@19.1.12)(react@19.1.1)':
+ '@mui/utils@7.3.2(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
- '@mui/types': 7.4.6(@types/react@19.1.12)
+ '@mui/types': 7.4.6(@types/react@19.1.14)
'@types/prop-types': 15.7.15
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-is: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@mui/x-date-pickers@8.12.0(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/x-date-pickers@8.12.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@mui/utils': 7.3.2(@types/react@19.1.12)(react@19.1.1)
- '@mui/x-internals': 8.12.0(@types/react@19.1.12)(react@19.1.1)
- '@types/react-transition-group': 4.4.12(@types/react@19.1.12)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/x-internals': 8.12.0(@types/react@19.1.14)(react@19.1.1)
+ '@types/react-transition-group': 4.4.12(@types/react@19.1.14)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
dayjs: 1.11.18
transitivePeerDependencies:
- '@types/react'
- '@mui/x-internals@8.12.0(@types/react@19.1.12)(react@19.1.1)':
+ '@mui/x-internals@8.12.0(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.3
- '@mui/utils': 7.3.2(@types/react@19.1.12)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
react: 19.1.1
reselect: 5.1.1
use-sync-external-store: 1.5.0(react@19.1.1)
@@ -10843,88 +10843,88 @@ snapshots:
'@radix-ui/primitive@1.1.3': {}
- '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.12)(react@19.1.1)':
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.14)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@radix-ui/react-context@1.1.2(@types/react@19.1.12)(react@19.1.1)':
+ '@radix-ui/react-context@1.1.2(@types/react@19.1.14)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@radix-ui/react-direction@1.1.1(@types/react@19.1.12)(react@19.1.1)':
+ '@radix-ui/react-direction@1.1.1(@types/react@19.1.14)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.14)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
- '@types/react': 19.1.12
- '@types/react-dom': 19.1.9(@types/react@19.1.12)
+ '@types/react': 19.1.14
+ '@types/react-dom': 19.1.9(@types/react@19.1.14)
- '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.14)(react@19.1.1)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.14)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
- '@types/react': 19.1.12
- '@types/react-dom': 19.1.9(@types/react@19.1.12)
+ '@types/react': 19.1.14
+ '@types/react-dom': 19.1.9(@types/react@19.1.14)
- '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
- '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.14)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
- '@types/react': 19.1.12
- '@types/react-dom': 19.1.9(@types/react@19.1.12)
+ '@types/react': 19.1.14
+ '@types/react-dom': 19.1.9(@types/react@19.1.14)
- '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@radix-ui/number': 1.1.1
'@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.14)(react@19.1.1)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.14)(react@19.1.1)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.14)(react@19.1.1)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.14)(react@19.1.1)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.14)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
- '@types/react': 19.1.12
- '@types/react-dom': 19.1.9(@types/react@19.1.12)
+ '@types/react': 19.1.14
+ '@types/react-dom': 19.1.9(@types/react@19.1.14)
- '@radix-ui/react-slot@1.2.3(@types/react@19.1.12)(react@19.1.1)':
+ '@radix-ui/react-slot@1.2.3(@types/react@19.1.14)(react@19.1.1)':
dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.14)(react@19.1.1)
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.12)(react@19.1.1)':
+ '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.14)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.12)(react@19.1.1)':
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.14)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
'@rolldown/pluginutils@1.0.0-beta.35': {}
@@ -11011,7 +11011,7 @@ snapshots:
'@rtsao/scc@1.1.0': {}
- '@rushstack/node-core-library@5.11.0(@types/node@24.3.1)':
+ '@rushstack/node-core-library@5.11.0(@types/node@24.5.2)':
dependencies:
ajv: 8.13.0
ajv-draft-04: 1.0.0(ajv@8.13.0)
@@ -11022,23 +11022,23 @@ snapshots:
resolve: 1.22.8
semver: 7.5.4
optionalDependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
'@rushstack/rig-package@0.5.3':
dependencies:
resolve: 1.22.8
strip-json-comments: 3.1.1
- '@rushstack/terminal@0.15.0(@types/node@24.3.1)':
+ '@rushstack/terminal@0.15.0(@types/node@24.5.2)':
dependencies:
- '@rushstack/node-core-library': 5.11.0(@types/node@24.3.1)
+ '@rushstack/node-core-library': 5.11.0(@types/node@24.5.2)
supports-color: 8.1.1
optionalDependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
- '@rushstack/ts-command-line@4.23.5(@types/node@24.3.1)':
+ '@rushstack/ts-command-line@4.23.5(@types/node@24.5.2)':
dependencies:
- '@rushstack/terminal': 0.15.0(@types/node@24.3.1)
+ '@rushstack/terminal': 0.15.0(@types/node@24.5.2)
'@types/argparse': 1.0.38
argparse: 1.0.10
string-argv: 0.3.2
@@ -11305,13 +11305,13 @@ snapshots:
'@tanstack/query-core': 5.90.2
react: 19.1.1
- '@tanstack/react-router-devtools@1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.132.7)(@types/node@24.3.1)(csstype@3.1.3)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass-embedded@1.93.2)(sass@1.93.2)(solid-js@1.9.5)(stylus@0.62.0)(terser@5.36.0)(tiny-invariant@1.3.3)(tsx@4.20.5)(yaml@2.8.1)':
+ '@tanstack/react-router-devtools@1.132.11(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.132.7)(@types/node@24.5.2)(csstype@3.1.3)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass-embedded@1.93.2)(sass@1.93.2)(solid-js@1.9.5)(stylus@0.62.0)(terser@5.36.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)':
dependencies:
'@tanstack/react-router': 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@tanstack/router-devtools-core': 1.132.7(@tanstack/router-core@1.132.7)(@types/node@24.3.1)(csstype@3.1.3)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(solid-js@1.9.5)(stylus@0.62.0)(terser@5.36.0)(tiny-invariant@1.3.3)(tsx@4.20.5)(yaml@2.8.1)
+ '@tanstack/router-devtools-core': 1.132.11(@tanstack/router-core@1.132.7)(@types/node@24.5.2)(csstype@3.1.3)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(solid-js@1.9.5)(stylus@0.62.0)(terser@5.36.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- '@tanstack/router-core'
- '@types/node'
@@ -11369,14 +11369,14 @@ snapshots:
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
- '@tanstack/router-devtools-core@1.132.7(@tanstack/router-core@1.132.7)(@types/node@24.3.1)(csstype@3.1.3)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(solid-js@1.9.5)(stylus@0.62.0)(terser@5.36.0)(tiny-invariant@1.3.3)(tsx@4.20.5)(yaml@2.8.1)':
+ '@tanstack/router-devtools-core@1.132.11(@tanstack/router-core@1.132.7)(@types/node@24.5.2)(csstype@3.1.3)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(solid-js@1.9.5)(stylus@0.62.0)(terser@5.36.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)':
dependencies:
'@tanstack/router-core': 1.132.7
clsx: 2.1.1
goober: 2.1.16(csstype@3.1.3)
solid-js: 1.9.5
tiny-invariant: 1.3.3
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
optionalDependencies:
csstype: 3.1.3
transitivePeerDependencies:
@@ -11400,12 +11400,12 @@ snapshots:
prettier: 3.6.2
recast: 0.23.11
source-map: 0.7.4
- tsx: 4.20.5
+ tsx: 4.20.6
zod: 3.25.76
transitivePeerDependencies:
- supports-color
- '@tanstack/router-plugin@1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
+ '@tanstack/router-plugin@1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))':
dependencies:
'@babel/core': 7.28.4
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4)
@@ -11423,7 +11423,7 @@ snapshots:
zod: 3.25.76
optionalDependencies:
'@tanstack/react-router': 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -11565,7 +11565,7 @@ snapshots:
'@types/adm-zip@0.5.7':
dependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
'@types/argparse@1.0.38': {}
@@ -11596,12 +11596,12 @@ snapshots:
dependencies:
'@types/http-cache-semantics': 4.0.4
'@types/keyv': 3.1.4
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
'@types/responselike': 1.0.3
'@types/conventional-commits-parser@5.0.0':
dependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
'@types/d3-array@3.2.1': {}
@@ -11737,7 +11737,7 @@ snapshots:
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
'@types/geojson@7946.0.14': {}
@@ -11757,11 +11757,11 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
'@types/keyv@3.1.4':
dependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
'@types/lodash-es@4.17.12':
dependencies:
@@ -11777,9 +11777,9 @@ snapshots:
'@types/node@16.18.108': {}
- '@types/node@24.3.1':
+ '@types/node@24.5.2':
dependencies:
- undici-types: 7.10.0
+ undici-types: 7.12.0
'@types/parse-json@4.0.2': {}
@@ -11793,21 +11793,21 @@ snapshots:
'@types/prop-types@15.7.15': {}
- '@types/react-dom@19.1.9(@types/react@19.1.12)':
+ '@types/react-dom@19.1.9(@types/react@19.1.14)':
dependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@types/react-transition-group@4.4.12(@types/react@19.1.12)':
+ '@types/react-transition-group@4.4.12(@types/react@19.1.14)':
dependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
- '@types/react@19.1.12':
+ '@types/react@19.1.14':
dependencies:
csstype: 3.1.3
'@types/responselike@1.0.3':
dependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
'@types/semver@7.7.1': {}
@@ -11825,7 +11825,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
optional: true
'@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
@@ -12040,7 +12040,7 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.10.1':
optional: true
- '@vitejs/plugin-legacy@7.2.1(terser@5.36.0)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
+ '@vitejs/plugin-legacy@7.2.1(terser@5.36.0)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))':
dependencies:
'@babel/core': 7.28.0
'@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.0)
@@ -12055,19 +12055,19 @@ snapshots:
regenerator-runtime: 0.14.1
systemjs: 6.15.1
terser: 5.36.0
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
- '@vitejs/plugin-react-swc@4.1.0(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
+ '@vitejs/plugin-react-swc@4.1.0(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.35
'@swc/core': 1.13.5
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- '@swc/helpers'
- '@vitejs/plugin-react@5.0.3(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
+ '@vitejs/plugin-react@5.0.3(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1))':
dependencies:
'@babel/core': 7.28.4
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4)
@@ -12075,7 +12075,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.35
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -12752,9 +12752,9 @@ snapshots:
core-js@3.45.1: {}
- cosmiconfig-typescript-loader@6.1.0(@types/node@24.3.1)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2):
+ cosmiconfig-typescript-loader@6.1.0(@types/node@24.5.2)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2):
dependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
cosmiconfig: 9.0.0(typescript@5.9.2)
jiti: 2.5.1
typescript: 5.9.2
@@ -14722,11 +14722,11 @@ snapshots:
jju@1.4.0: {}
- jotai@2.14.0(@babel/core@7.28.4)(@babel/template@7.27.2)(@types/react@19.1.12)(react@19.1.1):
+ jotai@2.15.0(@babel/core@7.28.4)(@babel/template@7.27.2)(@types/react@19.1.14)(react@19.1.1):
optionalDependencies:
'@babel/core': 7.28.4
'@babel/template': 7.27.2
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
react: 19.1.1
js-cookie@2.2.1: {}
@@ -14804,10 +14804,10 @@ snapshots:
kind-of@6.0.3: {}
- knip@5.64.1(@types/node@24.3.1)(typescript@5.9.2):
+ knip@5.64.1(@types/node@24.5.2)(typescript@5.9.2):
dependencies:
'@nodelib/fs.walk': 1.2.8
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
fast-glob: 3.3.3
formatly: 0.3.0
jiti: 2.6.0
@@ -15375,16 +15375,16 @@ snapshots:
muggle-string@0.4.1: {}
- mui-color-input@7.0.0(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
+ mui-color-input@7.0.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@ctrl/tinycolor': 4.1.0
- '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
nano-css@5.6.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
@@ -15977,14 +15977,14 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- react-hook-form-mui@8.0.0(a67848f6b3a393292e2dcb1a943d7b01):
+ react-hook-form-mui@8.0.0(5034abb0a696b1128593edad6cde9ffd):
dependencies:
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
react-hook-form: 7.52.1(react@19.1.1)
optionalDependencies:
- '@mui/icons-material': 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)
- '@mui/x-date-pickers': 8.12.0(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/icons-material': 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/x-date-pickers': 8.12.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-hook-form@7.52.1(react@19.1.1):
dependencies:
@@ -16004,11 +16004,11 @@ snapshots:
react-is@19.1.1: {}
- react-markdown@10.1.0(@types/react@19.1.12)(react@19.1.1):
+ react-markdown@10.1.0(@types/react@19.1.14)(react@19.1.1):
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
- '@types/react': 19.1.12
+ '@types/react': 19.1.14
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.0
html-url-attributes: 3.0.0
@@ -17035,7 +17035,7 @@ snapshots:
tslib@2.8.1: {}
- tsx@4.20.5:
+ tsx@4.20.6:
dependencies:
esbuild: 0.25.0
get-tsconfig: 4.10.1
@@ -17179,7 +17179,7 @@ snapshots:
has-symbols: 1.1.0
which-boxed-primitive: 1.1.1
- undici-types@7.10.0: {}
+ undici-types@7.12.0: {}
undici@5.29.0:
dependencies:
@@ -17392,7 +17392,7 @@ snapshots:
unist-util-stringify-position: 4.0.0
vfile-message: 4.0.2
- virtua@0.43.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5):
+ virtua@0.43.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5):
optionalDependencies:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
@@ -17408,9 +17408,9 @@ snapshots:
- rollup
- supports-color
- vite-plugin-dts@4.5.4(@types/node@24.3.1)(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)):
+ vite-plugin-dts@4.5.4(@types/node@24.5.2)(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)):
dependencies:
- '@microsoft/api-extractor': 7.51.0(@types/node@24.3.1)
+ '@microsoft/api-extractor': 7.51.0(@types/node@24.5.2)
'@rollup/pluginutils': 5.1.4(rollup@4.46.2)
'@volar/typescript': 2.4.11
'@vue/language-core': 2.2.0(typescript@5.9.2)
@@ -17421,13 +17421,13 @@ snapshots:
magic-string: 0.30.17
typescript: 5.9.2
optionalDependencies:
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- '@types/node'
- rollup
- supports-color
- vite-plugin-html@3.2.2(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)):
+ vite-plugin-html@3.2.2(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)):
dependencies:
'@rollup/pluginutils': 4.2.1
colorette: 2.0.20
@@ -17441,39 +17441,39 @@ snapshots:
html-minifier-terser: 6.1.0
node-html-parser: 5.4.2
pathe: 0.2.0
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
- vite-plugin-sass-dts@1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.93.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)):
+ vite-plugin-sass-dts@1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.93.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)):
dependencies:
postcss: 8.5.6
postcss-js: 4.0.1(postcss@8.5.6)
prettier: 3.6.2
sass-embedded: 1.93.2
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
- vite-plugin-svgr@4.5.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)):
+ vite-plugin-svgr@4.5.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)):
dependencies:
'@rollup/pluginutils': 5.2.0(rollup@4.46.2)
'@svgr/core': 8.1.0(typescript@5.9.2)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.2))
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- rollup
- supports-color
- typescript
- vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)):
+ vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)):
dependencies:
debug: 4.3.7
globrex: 0.1.2
tsconfck: 3.0.3(typescript@5.9.2)
optionalDependencies:
- vite: 7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
- typescript
- vite@7.1.7(@types/node@24.3.1)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
+ vite@7.1.7(@types/node@24.5.2)(jiti@2.6.0)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.6)(yaml@2.8.1):
dependencies:
esbuild: 0.25.0
fdir: 6.5.0(picomatch@4.0.3)
@@ -17482,7 +17482,7 @@ snapshots:
rollup: 4.46.2
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 24.3.1
+ '@types/node': 24.5.2
fsevents: 2.3.3
jiti: 2.6.0
less: 4.2.0
@@ -17491,7 +17491,7 @@ snapshots:
sass-embedded: 1.93.2
stylus: 0.62.0
terser: 5.36.0
- tsx: 4.20.5
+ tsx: 4.20.6
yaml: 2.8.1
void-elements@3.1.0: {}
diff --git a/lede/include/kernel-6.1 b/lede/include/kernel-6.1
index dbd08753a4..bc14ef8341 100644
--- a/lede/include/kernel-6.1
+++ b/lede/include/kernel-6.1
@@ -1,2 +1,2 @@
-LINUX_VERSION-6.1 = .153
-LINUX_KERNEL_HASH-6.1.153 = 76ebbde05899ff712856ae9b06413d71ab7162771e7bc6df4d4ab335e1fc9e48
+LINUX_VERSION-6.1 = .154
+LINUX_KERNEL_HASH-6.1.154 = 6f1aba92c3d0d21299eebb73161a2056c7c78c6ee77462a7cb705bd8cca8b198
diff --git a/lede/include/kernel-6.12 b/lede/include/kernel-6.12
index dd7f683f1a..438cd31bb8 100644
--- a/lede/include/kernel-6.12
+++ b/lede/include/kernel-6.12
@@ -1,2 +1,2 @@
-LINUX_VERSION-6.12 = .48
-LINUX_KERNEL_HASH-6.12.48 = 5bf9eb676751bf48978e38363c772298b41a75336d5038ed6d37012399471db2
+LINUX_VERSION-6.12 = .49
+LINUX_KERNEL_HASH-6.12.49 = 234621e146dacce2241049555d550e4f7a6bde67ccd7ef232d47ac8145425526
diff --git a/lede/include/kernel-6.6 b/lede/include/kernel-6.6
index ecfe1f667e..b1eaea13b2 100644
--- a/lede/include/kernel-6.6
+++ b/lede/include/kernel-6.6
@@ -1,2 +1,2 @@
-LINUX_VERSION-6.6 = .107
-LINUX_KERNEL_HASH-6.6.107 = c9b73017d3c7867b9c69a542e5318620cf2b14ed23d52f6aeaaee8aded9ee447
+LINUX_VERSION-6.6 = .108
+LINUX_KERNEL_HASH-6.6.108 = 601cd332aa695d16607b353feff369b4a0b36d111be077e8af54bdd41e1968a6
diff --git a/lede/include/version.mk b/lede/include/version.mk
index 3896ad0357..abd7bce7f3 100644
--- a/lede/include/version.mk
+++ b/lede/include/version.mk
@@ -23,13 +23,13 @@ PKG_CONFIG_DEPENDS += \
sanitize = $(call tolower,$(subst _,-,$(subst $(space),-,$(1))))
VERSION_NUMBER:=$(call qstrip,$(CONFIG_VERSION_NUMBER))
-VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.2)
+VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.3)
VERSION_CODE:=$(call qstrip,$(CONFIG_VERSION_CODE))
VERSION_CODE:=$(if $(VERSION_CODE),$(VERSION_CODE),$(REVISION))
VERSION_REPO:=$(call qstrip,$(CONFIG_VERSION_REPO))
-VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.2)
+VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.3)
VERSION_DIST:=$(call qstrip,$(CONFIG_VERSION_DIST))
VERSION_DIST:=$(if $(VERSION_DIST),$(VERSION_DIST),OpenWrt)
diff --git a/lede/package/base-files/image-config.in b/lede/package/base-files/image-config.in
index 4d5da196ae..73c62b349a 100644
--- a/lede/package/base-files/image-config.in
+++ b/lede/package/base-files/image-config.in
@@ -190,7 +190,7 @@ if VERSIONOPT
config VERSION_REPO
string
prompt "Release repository"
- default "https://downloads.openwrt.org/releases/24.10.2"
+ default "https://downloads.openwrt.org/releases/24.10.3"
help
This is the repository address embedded in the image, it defaults
to the trunk snapshot repo; the url may contain the following placeholders:
diff --git a/lede/target/linux/phytium/patches-5.10/001-add-phytium-support.patch b/lede/target/linux/phytium/patches-5.10/001-add-phytium-support.patch
index 4de812ae3a..b28f249dc1 100644
--- a/lede/target/linux/phytium/patches-5.10/001-add-phytium-support.patch
+++ b/lede/target/linux/phytium/patches-5.10/001-add-phytium-support.patch
@@ -322,14 +322,13 @@
}
--- a/arch/arm64/include/asm/cpucaps.h
+++ b/arch/arm64/include/asm/cpucaps.h
-@@ -69,7 +69,7 @@
- #define ARM64_SPECTRE_BHB 59
- #define ARM64_WORKAROUND_2457168 60
+@@ -71,6 +71,7 @@
#define ARM64_WORKAROUND_1742098 61
--
--#define ARM64_NCAPS 62
-+#define ARM64_HAS_ECV 62
-+#define ARM64_NCAPS 63
+ #define ARM64_WORKAROUND_SPECULATIVE_SSBS 62
+
+-#define ARM64_NCAPS 63
++#define ARM64_HAS_ECV 63
++#define ARM64_NCAPS 64
#endif /* __ASM_CPUCAPS_H */
--- a/arch/arm64/include/asm/esr.h
@@ -2348,9 +2347,9 @@
struct clk *tsu_clk;
struct net_device *dev;
+ struct ncsi_dev *ndev;
+ /* Protects hw_stats and ethtool_stats */
+ spinlock_t stats_lock;
union {
- struct macb_stats macb;
- struct gem_stats gem;
@@ -1201,7 +1299,13 @@ struct macb {
struct mii_bus *mii_bus;
struct phylink *phylink;
diff --git a/nodepass/README.md b/nodepass/README.md
index 5efb44fc71..b2f1bb5c3a 100644
--- a/nodepass/README.md
+++ b/nodepass/README.md
@@ -1,5 +1,5 @@
-

+

[](https://github.com/avelino/awesome-go#networking)
[](https://github.com/yosebyte/nodepass/releases)
@@ -10,6 +10,8 @@
[](https://deepwiki.com/yosebyte/nodepass)

+

+
English | [简体中文](README_zh.md)
@@ -98,6 +100,8 @@ The [NodePassProject](https://github.com/NodePassProject) organization develops
- **[npsh](https://github.com/NodePassProject/npsh)**: A collection of one-click scripts that provide simple deployment for API or Dashboard with flexible configuration and management.
+- **[NodePass-ApplePlatforms](https://github.com/NodePassProject/NodePass-ApplePlatforms)**: An iOS/macOS application that offers a native experience for Apple users.
+
- **[nodepass-core](https://github.com/NodePassProject/nodepass-core)**: Development branch, featuring previews of new functionalities and performance optimizations, suitable for advanced users and developers.
## 💬 Discussion
diff --git a/nodepass/README_zh.md b/nodepass/README_zh.md
index fb709cae75..f264de7c30 100644
--- a/nodepass/README_zh.md
+++ b/nodepass/README_zh.md
@@ -10,6 +10,8 @@
[](https://deepwiki.com/yosebyte/nodepass)

+
+
[English](README.md) | 简体中文
@@ -98,6 +100,8 @@ nodepass "master://:10101/api?log=debug&tls=1"
- **[npsh](https://github.com/NodePassProject/npsh)**: 简单易用的 NodePass 一键脚本合集,包括 API 主控、Dash 面板的安装部署、灵活配置和辅助管理。
+- **[NodePass-ApplePlatforms](https://github.com/NodePassProject/NodePass-ApplePlatforms)**: 一款为 Apple 用户提供原生体验的 iOS/macOS 应用程序。
+
- **[nodepass-core](https://github.com/NodePassProject/nodepass-core)**: 开发分支,包含新功能预览和性能优化测试,适合高级用户和开发者。
## 💬 讨论
diff --git a/nodepass/cmd/nodepass/core.go b/nodepass/cmd/nodepass/core.go
index 68e8197346..830484ca5d 100644
--- a/nodepass/cmd/nodepass/core.go
+++ b/nodepass/cmd/nodepass/core.go
@@ -2,37 +2,80 @@ package main
import (
"crypto/tls"
+ "fmt"
"net/url"
+ "os"
+ "runtime"
"time"
"github.com/NodePassProject/cert"
+ "github.com/NodePassProject/logs"
"github.com/yosebyte/nodepass/internal"
)
-// coreDispatch 根据URL方案分派到不同的运行模式
-func coreDispatch(parsedURL *url.URL) {
- var core interface{ Run() }
+// start 启动核心逻辑
+func start(args []string) error {
+ if len(args) != 2 {
+ return fmt.Errorf("start: empty URL command")
+ }
- switch scheme := parsedURL.Scheme; scheme {
- case "server", "master":
- tlsCode, tlsConfig := getTLSProtocol(parsedURL)
- if scheme == "server" {
- core = internal.NewServer(parsedURL, tlsCode, tlsConfig, logger)
- } else {
- core = internal.NewMaster(parsedURL, tlsCode, tlsConfig, logger, version)
- }
- case "client":
- core = internal.NewClient(parsedURL, logger)
- default:
- logger.Error("Unknown core: %v", scheme)
- getExitInfo()
+ parsedURL, err := url.Parse(args[1])
+ if err != nil {
+ return fmt.Errorf("start: parse URL failed: %v", err)
+ }
+
+ logger := initLogger(parsedURL.Query().Get("log"))
+
+ core, err := createCore(parsedURL, logger)
+ if err != nil {
+ return fmt.Errorf("start: create core failed: %v", err)
}
core.Run()
+ return nil
+}
+
+// initLogger 初始化日志记录器
+func initLogger(level string) *logs.Logger {
+ logger := logs.NewLogger(logs.Info, true)
+ switch level {
+ case "none":
+ logger.SetLogLevel(logs.None)
+ case "debug":
+ logger.SetLogLevel(logs.Debug)
+ logger.Debug("Init log level: DEBUG")
+ case "warn":
+ logger.SetLogLevel(logs.Warn)
+ logger.Warn("Init log level: WARN")
+ case "error":
+ logger.SetLogLevel(logs.Error)
+ logger.Error("Init log level: ERROR")
+ case "event":
+ logger.SetLogLevel(logs.Event)
+ logger.Event("Init log level: EVENT")
+ default:
+ }
+ return logger
+}
+
+// createCore 创建核心
+func createCore(parsedURL *url.URL, logger *logs.Logger) (interface{ Run() }, error) {
+ switch parsedURL.Scheme {
+ case "server":
+ tlsCode, tlsConfig := getTLSProtocol(parsedURL, logger)
+ return internal.NewServer(parsedURL, tlsCode, tlsConfig, logger)
+ case "client":
+ return internal.NewClient(parsedURL, logger)
+ case "master":
+ tlsCode, tlsConfig := getTLSProtocol(parsedURL, logger)
+ return internal.NewMaster(parsedURL, tlsCode, tlsConfig, logger, version)
+ default:
+ return nil, fmt.Errorf("unknown core: %v", parsedURL)
+ }
}
// getTLSProtocol 获取TLS配置
-func getTLSProtocol(parsedURL *url.URL) (string, *tls.Config) {
+func getTLSProtocol(parsedURL *url.URL, logger *logs.Logger) (string, *tls.Config) {
// 生成基本TLS配置
tlsConfig, err := cert.NewTLSConfig(version)
if err != nil {
@@ -99,3 +142,40 @@ func getTLSProtocol(parsedURL *url.URL) (string, *tls.Config) {
return "0", nil
}
}
+
+// exit 退出程序并显示帮助信息
+func exit(err error) {
+ errMsg1, errMsg2 := "", ""
+ if err != nil {
+ errStr := "FAILED: " + err.Error()
+ if len(errStr) > 35 {
+ errMsg1 = errStr[:35]
+ if len(errStr) > 70 {
+ errMsg2 = errStr[35:67] + "..."
+ } else {
+ errMsg2 = errStr[35:]
+ }
+ } else {
+ errMsg1 = errStr
+ }
+ }
+ fmt.Printf(`
+╭─────────────────────────────────────╮
+│ ░░█▀█░█▀█░░▀█░█▀▀░█▀█░█▀█░█▀▀░█▀▀░░ │
+│ ░░█░█░█░█░█▀█░█▀▀░█▀▀░█▀█░▀▀█░▀▀█░░ │
+│ ░░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀░░░▀░▀░▀▀▀░▀▀▀░░ │
+├─────────────────────────────────────┤
+│%*s │
+│%*s │
+├─────────────────────────────────────┤
+│ server://password@host/host? │
+│ client://password@host/host? │
+│ master://hostname:port/path? │
+├─────────────────────────────────────┤
+│ %-35s │
+│ %-35s │
+╰─────────────────────────────────────╯
+
+`, 36, version, 36, fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), errMsg1, errMsg2)
+ os.Exit(1)
+}
diff --git a/nodepass/cmd/nodepass/main.go b/nodepass/cmd/nodepass/main.go
index d677e1dbcb..231f525370 100644
--- a/nodepass/cmd/nodepass/main.go
+++ b/nodepass/cmd/nodepass/main.go
@@ -1,84 +1,11 @@
package main
-import (
- "net/url"
- "os"
- "runtime"
+import "os"
- "github.com/NodePassProject/logs"
-)
+var version = "dev"
-var (
- // 全局日志记录器
- logger = logs.NewLogger(logs.Info, true)
- // 程序版本
- version = "dev"
-)
-
-// main 程序入口
func main() {
- parsedURL := getParsedURL(os.Args)
- initLogLevel(parsedURL.Query().Get("log"))
- coreDispatch(parsedURL)
-}
-
-// getParsedURL 解析URL参数
-func getParsedURL(args []string) *url.URL {
- if len(args) < 2 {
- getExitInfo()
- }
-
- parsedURL, err := url.Parse(args[1])
- if err != nil {
- logger.Error("URL parse: %v", err)
- getExitInfo()
- }
-
- return parsedURL
-}
-
-// initLogLevel 初始化日志级别
-func initLogLevel(level string) {
- switch level {
- case "none":
- logger.SetLogLevel(logs.None)
- case "debug":
- logger.SetLogLevel(logs.Debug)
- logger.Debug("Init log level: DEBUG")
- case "warn":
- logger.SetLogLevel(logs.Warn)
- logger.Warn("Init log level: WARN")
- case "error":
- logger.SetLogLevel(logs.Error)
- logger.Error("Init log level: ERROR")
- case "event":
- logger.SetLogLevel(logs.Event)
- logger.Event("Init log level: EVENT")
- default:
- logger.SetLogLevel(logs.Info)
- logger.Info("Init log level: INFO")
+ if err := start(os.Args); err != nil {
+ exit(err)
}
}
-
-// getExitInfo 输出帮助信息并退出程序
-func getExitInfo() {
- logger.SetLogLevel(logs.Info)
- logger.Info(`Version: %v %v/%v
-
-╭─────────────────────────────────────────────╮
-│ ░░█▀█░█▀█░░▀█░█▀▀░█▀█░█▀█░█▀▀░█▀▀░░ │
-│ ░░█░█░█░█░█▀█░█▀▀░█▀▀░█▀█░▀▀█░▀▀█░░ │
-│ ░░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀░░░▀░▀░▀▀▀░▀▀▀░░ │
-├─────────────────────────────────────────────┤
-│ >Universal TCP/UDP Tunneling Solution │
-│ >https://github.com/yosebyte/nodepass │
-├─────────────────────────────────────────────┤
-│ Usage: nodepass "" │
-├─────────────────────────────────────────────┤
-│ server://password@host/host?& │
-│ client://password@host/host?& │
-│ master://hostname:port/path?& │
-╰─────────────────────────────────────────────╯
-`, version, runtime.GOOS, runtime.GOARCH)
- os.Exit(1)
-}
diff --git a/nodepass/docs/en/api.md b/nodepass/docs/en/api.md
index 6fcdeb4edd..37c4ca04cf 100644
--- a/nodepass/docs/en/api.md
+++ b/nodepass/docs/en/api.md
@@ -42,6 +42,7 @@ nodepass "master://0.0.0.0:9090/admin?log=info&tls=1"
| `/instances/{id}` | DELETE | Delete instance |
| `/events` | GET | SSE real-time event stream |
| `/info` | GET | Get master service info |
+| `/info` | POST | Update master alias |
| `/tcping` | GET | TCP connection test |
| `/openapi.json` | GET | OpenAPI specification |
| `/docs` | GET | Swagger UI documentation |
@@ -64,7 +65,13 @@ API Key authentication is enabled by default, automatically generated and saved
"type": "client|server",
"status": "running|stopped|error",
"url": "...",
+ "config": "server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0",
"restart": true,
+ "tags": [
+ {"key": "environment", "value": "production"},
+ {"key": "region", "value": "us-west-2"},
+ {"key": "project", "value": "web-service"}
+ ],
"mode": 0,
"ping": 0,
"pool": 0,
@@ -81,7 +88,9 @@ API Key authentication is enabled by default, automatically generated and saved
- `ping`/`pool`: Health check data
- `tcps`/`udps`: Current active connection count statistics
- `tcprx`/`tcptx`/`udprx`/`udptx`: Cumulative traffic statistics
+- `config`: Instance configuration URL with complete startup configuration
- `restart`: Auto-restart policy
+- `tags`: Optional key-value pairs for labeling and organizing instances
### Instance URL Format
@@ -514,6 +523,31 @@ To properly manage lifecycles:
return data.success;
}
+ // Update instance tags
+ async function updateInstanceTags(instanceId, tags) {
+ const response = await fetch(`${API_URL}/instances/${instanceId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-API-Key': apiKey // If API Key is enabled
+ },
+ body: JSON.stringify({ tags })
+ });
+
+ const data = await response.json();
+ return data.success;
+ }
+
+ // Delete specific tags by setting their values to empty string
+ async function deleteInstanceTags(instanceId, tagKeys) {
+ const tagsToDelete = {};
+ tagKeys.forEach(key => {
+ tagsToDelete[key] = ""; // Empty string removes the tag
+ });
+
+ return await updateInstanceTags(instanceId, tagsToDelete);
+ }
+
// Update instance URL configuration
async function updateInstanceURL(instanceId, newURL) {
const response = await fetch(`${API_URL}/instances/${instanceId}`, {
@@ -530,7 +564,44 @@ To properly manage lifecycles:
}
```
-5. **Auto-restart Policy Management**: Configure automatic startup behavior
+5. **Tag Management**: Label and organize instances using key-value pairs
+ ```javascript
+ // Update instance tags via PATCH method
+ async function updateInstanceTags(instanceId, tags) {
+ const response = await fetch(`${API_URL}/instances/${instanceId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-API-Key': apiKey
+ },
+ body: JSON.stringify({ tags })
+ });
+
+ return response.json();
+ }
+
+ // Add or update tags - existing tags are preserved unless specified
+ await updateInstanceTags('abc123', [
+ {"key": "environment", "value": "production"}, // Add/update
+ {"key": "region", "value": "us-west-2"}, // Add/update
+ {"key": "team", "value": "backend"} // Add/update
+ ]);
+
+ // Delete specific tags by setting empty values
+ await updateInstanceTags('abc123', [
+ {"key": "old-tag", "value": ""}, // Delete this tag
+ {"key": "temp-env", "value": ""} // Delete this tag
+ ]);
+
+ // Mix operations: add/update some tags, delete others
+ await updateInstanceTags('abc123', [
+ {"key": "environment", "value": "staging"}, // Update existing
+ {"key": "version", "value": "2.0"}, // Add new
+ {"key": "deprecated", "value": ""} // Delete existing
+ ]);
+ ```
+
+6. **Auto-restart Policy Management**: Configure automatic startup behavior
```javascript
async function setAutoStartPolicy(instanceId, enableAutoStart) {
const response = await fetch(`${API_URL}/instances/${instanceId}`, {
@@ -782,6 +853,17 @@ async function configureAutoStartPolicies(instances) {
}
```
+### Tag Management Rules
+
+1. **Merge-based Updates**: Tags are processed with merge logic - existing tags are preserved unless explicitly updated or deleted
+2. **Array Format**: Tags use array format `[{"key": "key1", "value": "value1"}]`
+3. **Key Filtering**: Empty keys are automatically filtered out and rejected by validation
+4. **Delete by Empty Value**: Set `value: ""` (empty string) to delete an existing tag key
+5. **Add/Update Logic**: Non-empty values will add new tags or update existing ones
+6. **Uniqueness Check**: Duplicate keys are not allowed within the same tag operation
+7. **Limits**: Maximum 50 tags, key names ≤100 characters, values ≤500 characters
+8. **Persistence**: All tag operations are automatically saved to disk and restored after restart
+
## Instance Data Structure
The instance object in API responses contains the following fields:
@@ -793,7 +875,13 @@ The instance object in API responses contains the following fields:
"type": "server", // Instance type: server or client
"status": "running", // Instance status: running, stopped, or error
"url": "server://...", // Instance configuration URL
+ "config": "server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0", // Complete configuration URL
"restart": true, // Auto-restart policy
+ "tags": [ // Tag array
+ {"key": "environment", "value": "production"},
+ {"key": "project", "value": "web-service"},
+ {"key": "region", "value": "us-west-2"}
+ ],
"mode": 0, // Instance mode
"tcprx": 1024, // TCP received bytes
"tcptx": 2048, // TCP transmitted bytes
@@ -804,8 +892,31 @@ The instance object in API responses contains the following fields:
**Note:**
- `alias` field is optional, empty string if not set
+- `config` field contains the instance's complete configuration URL, auto-generated by the system
- `mode` field indicates the current runtime mode of the instance
- `restart` field controls the auto-restart behavior of the instance
+- `tags` field is optional, only included when tags are present
+
+### Instance Configuration Field
+
+NodePass Master automatically maintains the `config` field for each instance:
+
+- **Auto Generation**: Automatically generated when instances are created and updated, no manual maintenance required
+- **Complete Configuration**: Contains the instance's complete URL with all default parameters
+- **Configuration Inheritance**: log and tls configurations are inherited from master settings
+- **Default Parameters**: Other parameters use system defaults
+- **Read-Only Nature**: Auto-generated field that cannot be directly modified through the API
+
+**Example config field value:**
+```
+server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0
+```
+
+This feature is particularly useful for:
+- Configuration backup and export
+- Instance configuration integrity checks
+- Automated deployment scripts
+- Configuration documentation generation
## System Information Endpoint
@@ -825,13 +936,14 @@ The response contains the following system information fields:
```json
{
+ "alias": "dev", // Master alias
"os": "linux", // Operating system type
"arch": "amd64", // System architecture
"cpu": 45, // CPU usage percentage (Linux only)
"mem_total": 8589934592, // Total memory in bytes (Linux only)
- "mem_free": 2684354560, // Free memory in bytes (Linux only)
+ "mem_used": 2684354560, // Used memory in bytes (Linux only)
"swap_total": 3555328000, // Total swap space in bytes (Linux only)
- "swap_free": 3555328000, // Free swap space in bytes (Linux only)
+ "swap_used": 3555328000, // Used swap space in bytes (Linux only)
"netrx": 1048576000, // Network received bytes (cumulative, Linux only)
"nettx": 2097152000, // Network transmitted bytes (cumulative, Linux only)
"diskr": 4194304000, // Disk read bytes (cumulative, Linux only)
@@ -879,12 +991,16 @@ function displaySystemStatus() {
console.log(`CPU usage: ${info.cpu}%`);
}
if (info.mem_total > 0) {
- const memUsagePercent = ((info.mem_total - info.mem_free) / info.mem_total * 100).toFixed(1);
- console.log(`Memory usage: ${memUsagePercent}% (${(info.mem_free / 1024 / 1024 / 1024).toFixed(1)}GB free of ${(info.mem_total / 1024 / 1024 / 1024).toFixed(1)}GB total)`);
+ const memUsagePercent = (info.mem_used / info.mem_total * 100).toFixed(1);
+ const memFreeGB = ((info.mem_total - info.mem_used) / 1024 / 1024 / 1024).toFixed(1);
+ const memTotalGB = (info.mem_total / 1024 / 1024 / 1024).toFixed(1);
+ console.log(`Memory usage: ${memUsagePercent}% (${memFreeGB}GB free of ${memTotalGB}GB total)`);
}
if (info.swap_total > 0) {
- const swapUsagePercent = ((info.swap_total - info.swap_free) / info.swap_total * 100).toFixed(1);
- console.log(`Swap usage: ${swapUsagePercent}% (${(info.swap_free / 1024 / 1024 / 1024).toFixed(1)}GB free of ${(info.swap_total / 1024 / 1024 / 1024).toFixed(1)}GB total)`);
+ const swapUsagePercent = (info.swap_used / info.swap_total * 100).toFixed(1);
+ const swapFreeGB = ((info.swap_total - info.swap_used) / 1024 / 1024 / 1024).toFixed(1);
+ const swapTotalGB = (info.swap_total / 1024 / 1024 / 1024).toFixed(1);
+ console.log(`Swap usage: ${swapUsagePercent}% (${swapFreeGB}GB free of ${swapTotalGB}GB total)`);
}
} else {
console.log('CPU, memory, swap space, network I/O, disk I/O, and system uptime monitoring is only available on Linux systems');
@@ -910,8 +1026,8 @@ function displaySystemStatus() {
- **Log level verification**: Ensure current log level meets expectations
- **Resource monitoring**: On Linux systems, monitor CPU, memory, swap space, network I/O, disk I/O usage to ensure optimal performance
- CPU usage is calculated by parsing `/proc/stat` (percentage of non-idle time)
- - Memory information is obtained by parsing `/proc/meminfo` (total and free memory in bytes)
- - Swap space information is obtained by parsing `/proc/meminfo` (total and free swap space in bytes)
+ - Memory information is obtained by parsing `/proc/meminfo` (total and used memory in bytes, calculated as total minus available)
+ - Swap space information is obtained by parsing `/proc/meminfo` (total and used swap space in bytes, calculated as total minus free)
- Network I/O is calculated by parsing `/proc/net/dev` (cumulative bytes, excluding virtual interfaces)
- Disk I/O is calculated by parsing `/proc/diskstats` (cumulative bytes, major devices only)
- System uptime is obtained by parsing `/proc/uptime`
@@ -987,13 +1103,12 @@ const instance = await fetch(`${API_URL}/instances/abc123`, {
```
#### PATCH /instances/{id}
-- **Description**: Update instance state, alias, or perform control operations
+- **Description**: Update instance state, alias, tags, or perform control operations
- **Authentication**: Requires API Key
-- **Request body**: `{ "alias": "new alias", "action": "start|stop|restart|reset", "restart": true|false }`
-- **Features**: Does not interrupt running instances, only updates specified fields. `action: "reset"` can reset traffic statistics (tcprx, tcptx, udprx, udptx) for the instance to zero.
+- **Request body**: `{ "alias": "new alias", "action": "start|stop|restart|reset", "restart": true|false, "tags": [{"key": "key1", "value": "value1"}] }`
- **Example**:
```javascript
-// Update alias and auto-restart policy
+// Update tags
await fetch(`${API_URL}/instances/abc123`, {
method: 'PATCH',
headers: {
@@ -1001,29 +1116,27 @@ await fetch(`${API_URL}/instances/abc123`, {
'X-API-Key': apiKey
},
body: JSON.stringify({
- alias: 'Web Server',
- restart: true
+ tags: [
+ {"key": "environment", "value": "production"},
+ {"key": "region", "value": "us-west-2"}
+ ]
})
});
-// Control instance operations
+// Update and delete tags in one operation
await fetch(`${API_URL}/instances/abc123`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
- body: JSON.stringify({ action: 'restart' })
-});
-
-// Reset traffic statistics
-await fetch(`${API_URL}/instances/abc123`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'X-API-Key': apiKey
- },
- body: JSON.stringify({ action: 'reset' })
+ body: JSON.stringify({
+ tags: [
+ {"key": "environment", "value": "staging"}, // Update existing
+ {"key": "version", "value": "2.0"}, // Add new
+ {"key": "old-tag", "value": ""} // Delete existing
+ ]
+ })
});
```
@@ -1074,6 +1187,28 @@ await fetch(`${API_URL}/instances/abc123`, {
- **Authentication**: Requires API Key
- **Response**: Contains system information, version, uptime, CPU and RAM usage, etc.
+#### POST /info
+- **Description**: Update master alias
+- **Authentication**: Requires API Key
+- **Request body**: `{ "alias": "new alias" }`
+- **Response**: Complete master information (same as GET /info)
+- **Example**:
+```javascript
+// Update master alias
+const response = await fetch(`${API_URL}/info`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-API-Key': apiKey
+ },
+ body: JSON.stringify({ alias: 'My NodePass Server' })
+});
+
+const data = await response.json();
+console.log('Updated alias:', data.alias);
+// Response contains full system info with updated alias
+```
+
#### GET /tcping
- **Description**: TCP connection test, checks connectivity and latency to target address
- **Authentication**: Requires API Key
@@ -1130,9 +1265,10 @@ Examples:
| `tls` | TLS encryption level | `0`(none), `1`(self-signed), `2`(certificate) | `0` | Server only |
| `crt` | Certificate path | File path | None | Server only |
| `key` | Private key path | File path | None | Server only |
-| `mode` | Runtime mode control | `0`(auto), `1`(force mode 1), `2`(force mode 2) | `0` | Both |
| `min` | Minimum pool capacity | Integer > 0 | `64` | Client dual-end handshake mode only |
| `max` | Maximum pool capacity | Integer > 0 | `1024` | Dual-end handshake mode |
+| `mode` | Runtime mode control | `0`(auto), `1`(force mode 1), `2`(force mode 2) | `0` | Both |
| `read` | Read timeout duration | Time duration (e.g., `10m`, `30s`, `1h`) | `10m` | Both |
| `rate` | Bandwidth rate limit | Integer (Mbps), 0=unlimited | `0` | Both |
+| `slot` | Connection slot count | Integer (1-65536) | `65536` | Both |
| `proxy` | PROXY protocol support | `0`(disabled), `1`(enabled) | `0` | Both |
\ No newline at end of file
diff --git a/nodepass/docs/en/configuration.md b/nodepass/docs/en/configuration.md
index 8f19b23c13..d8a779ba64 100644
--- a/nodepass/docs/en/configuration.md
+++ b/nodepass/docs/en/configuration.md
@@ -254,19 +254,19 @@ nodepass "server://0.0.0.0:10101/0.0.0.0:8080?log=info&tls=1&proxy=1&rate=100"
NodePass allows flexible configuration via URL query parameters. The following table shows which parameters are applicable in server, client, and master modes:
-| Parameter | Description | server | client | master |
-|-----------|----------------------|:------:|:------:|:------:|
-| `log` | Log level | O | O | O |
-| `tls` | TLS encryption mode | O | X | O |
-| `crt` | Custom certificate path| O | X | O |
-| `key` | Custom key path | O | X | O |
-| `min` | Minimum pool capacity | X | O | X |
-| `max` | Maximum pool capacity | O | X | X |
-| `mode` | Run mode control | O | O | X |
-| `read` | Data read timeout | O | O | X |
-| `rate` | Bandwidth rate limit | O | O | X |
-| `slot` | Maximum connection limit | O | O | X |
-| `proxy` | PROXY protocol support| O | O | X |
+| Parameter | Description | Default | server | client | master |
+|-----------|----------------------|---------|:------:|:------:|:------:|
+| `log` | Log level | `info` | O | O | O |
+| `tls` | TLS encryption mode | `0` | O | X | O |
+| `crt` | Custom certificate path| N/A | O | X | O |
+| `key` | Custom key path | N/A | O | X | O |
+| `min` | Minimum pool capacity | `64` | X | O | X |
+| `max` | Maximum pool capacity | `1024` | O | X | X |
+| `mode` | Run mode control | `0` | O | O | X |
+| `read` | Data read timeout | `1h` | O | O | X |
+| `rate` | Bandwidth rate limit | `0` | O | O | X |
+| `slot` | Maximum connection limit | `65536` | O | O | X |
+| `proxy` | PROXY protocol support| `0` | O | O | X |
- O: Parameter is valid and recommended for configuration
- X: Parameter is not applicable and should be ignored
@@ -285,6 +285,7 @@ NodePass behavior can be fine-tuned using environment variables. Below is the co
| Variable | Description | Default | Example |
|----------|-------------|---------|---------|
| `NP_SEMAPHORE_LIMIT` | Signal channel buffer size | 65536 | `export NP_SEMAPHORE_LIMIT=2048` |
+| `NP_TCP_DATA_BUF_SIZE` | Buffer size for TCP data transfer | 32768 | `export NP_TCP_DATA_BUF_SIZE=65536` |
| `NP_UDP_DATA_BUF_SIZE` | Buffer size for UDP packets | 2048 | `export NP_UDP_DATA_BUF_SIZE=16384` |
| `NP_HANDSHAKE_TIMEOUT` | Timeout for handshake operations | 10s | `export NP_HANDSHAKE_TIMEOUT=30s` |
| `NP_TCP_DIAL_TIMEOUT` | Timeout for establishing TCP connections | 30s | `export NP_TCP_DIAL_TIMEOUT=60s` |
@@ -348,6 +349,11 @@ For applications relying heavily on UDP traffic:
For optimizing TCP connections:
+- `NP_TCP_DATA_BUF_SIZE`: Buffer size for TCP data transfer
+ - Default (32768) provides good balance for most applications
+ - Increase for high-throughput applications requiring larger buffers
+ - Consider increasing to 65536 or higher for bulk data transfers and streaming
+
- `NP_TCP_DIAL_TIMEOUT`: Timeout for establishing TCP connections
- Default (30s) is suitable for most network conditions
- Increase for unstable network conditions
@@ -401,6 +407,7 @@ Environment variables:
export NP_MIN_POOL_INTERVAL=50ms
export NP_MAX_POOL_INTERVAL=500ms
export NP_SEMAPHORE_LIMIT=8192
+export NP_TCP_DATA_BUF_SIZE=65536
export NP_UDP_DATA_BUF_SIZE=32768
export NP_POOL_GET_TIMEOUT=60s
export NP_REPORT_INTERVAL=10s
diff --git a/nodepass/docs/en/usage.md b/nodepass/docs/en/usage.md
index 94015b9bf7..c66662cbae 100644
--- a/nodepass/docs/en/usage.md
+++ b/nodepass/docs/en/usage.md
@@ -327,7 +327,7 @@ NodePass uses tunnel keys to authenticate connections between clients and server
The handshake process between client and server is as follows:
1. **Client Connection**: Client connects to the server's tunnel address
-2. **Key Authentication**: Client sends XOR-encrypted tunnel key
+2. **Key Authentication**: Client sends encrypted tunnel key
3. **Server Verification**: Server decrypts and verifies if the key matches
4. **Configuration Sync**: Upon successful verification, server sends tunnel configuration including:
- Data flow direction
diff --git a/nodepass/docs/zh/api.md b/nodepass/docs/zh/api.md
index fb7c7528ef..00723d4df9 100644
--- a/nodepass/docs/zh/api.md
+++ b/nodepass/docs/zh/api.md
@@ -42,6 +42,7 @@ nodepass "master://0.0.0.0:9090/admin?log=info&tls=1"
| `/instances/{id}` | DELETE | 删除实例 |
| `/events` | GET | SSE 实时事件流 |
| `/info` | GET | 获取主控服务信息 |
+| `/info` | POST | 更新主控别名 |
| `/tcping` | GET | TCP连接测试 |
| `/openapi.json` | GET | OpenAPI 规范 |
| `/docs` | GET | Swagger UI 文档 |
@@ -64,7 +65,13 @@ API Key 认证默认启用,首次启动自动生成并保存在 `nodepass.gob`
"type": "client|server",
"status": "running|stopped|error",
"url": "...",
+ "config": "server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0",
"restart": true,
+ "tags": [
+ {"key": "environment", "value": "production"},
+ {"key": "region", "value": "us-west-2"},
+ {"key": "project", "value": "web-service"}
+ ],
"mode": 0,
"ping": 0,
"pool": 0,
@@ -81,7 +88,9 @@ API Key 认证默认启用,首次启动自动生成并保存在 `nodepass.gob`
- `ping`/`pool`:健康检查数据
- `tcps`/`udps`:当前活动连接数统计
- `tcprx`/`tcptx`/`udprx`/`udptx`:累计流量统计
+- `config`:实例配置URL,包含完整的启动配置
- `restart`:自启动策略
+- `tags`:可选的键值对,用于标记和组织实例
### 实例 URL 格式
@@ -112,6 +121,7 @@ API Key 认证默认启用,首次启动自动生成并保存在 `nodepass.gob`
- 所有实例、流量、健康检查、别名、自启动策略均持久化存储,重启后自动恢复
- API 详细规范见 `/openapi.json`,Swagger UI 见 `/docs`
+
```javascript
// 重新生成API Key(需要知道当前的API Key)
async function regenerateApiKey() {
@@ -513,6 +523,31 @@ NodePass主控模式提供自动备份功能,定期备份状态文件以防止
return data.success;
}
+ // 更新实例标签
+ async function updateInstanceTags(instanceId, tags) {
+ const response = await fetch(`${API_URL}/instances/${instanceId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-API-Key': apiKey // 如果启用了API Key
+ },
+ body: JSON.stringify({ tags })
+ });
+
+ const data = await response.json();
+ return data.success;
+ }
+
+ // 通过设置空字符串值删除特定标签
+ async function deleteInstanceTags(instanceId, tagKeys) {
+ const tagsToDelete = {};
+ tagKeys.forEach(key => {
+ tagsToDelete[key] = ""; // 空字符串会删除标签
+ });
+
+ return await updateInstanceTags(instanceId, tagsToDelete);
+ }
+
// 更新实例URL配置
async function updateInstanceURL(instanceId, newURL) {
const response = await fetch(`${API_URL}/instances/${instanceId}`, {
@@ -529,7 +564,44 @@ NodePass主控模式提供自动备份功能,定期备份状态文件以防止
}
```
-5. **自启动策略管理**:配置自动启动行为
+5. **标签管理**:使用键值对标记和组织实例
+ ```javascript
+ // 通过PATCH方法更新实例标签
+ async function updateInstanceTags(instanceId, tags) {
+ const response = await fetch(`${API_URL}/instances/${instanceId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-API-Key': apiKey
+ },
+ body: JSON.stringify({ tags })
+ });
+
+ return response.json();
+ }
+
+ // 添加或更新标签 - 现有标签会保留,除非被显式指定
+ await updateInstanceTags('abc123', [
+ {"key": "environment", "value": "production"}, // 添加/更新
+ {"key": "region", "value": "us-west-2"}, // 添加/更新
+ {"key": "team", "value": "backend"} // 添加/更新
+ ]);
+
+ // 通过设置空值删除特定标签
+ await updateInstanceTags('abc123', [
+ {"key": "old-tag", "value": ""}, // 删除此标签
+ {"key": "temp-env", "value": ""} // 删除此标签
+ ]);
+
+ // 混合操作:添加/更新某些标签,删除其他标签
+ await updateInstanceTags('abc123', [
+ {"key": "environment", "value": "staging"}, // 更新现有
+ {"key": "version", "value": "2.0"}, // 添加新的
+ {"key": "deprecated", "value": ""} // 删除现有
+ ]);
+ ```
+
+6. **自启动策略管理**:配置自动启动行为
```javascript
async function setAutoStartPolicy(instanceId, enableAutoStart) {
const response = await fetch(`${API_URL}/instances/${instanceId}`, {
@@ -781,6 +853,17 @@ async function configureAutoStartPolicies(instances) {
}
```
+### 标签管理规则
+
+1. **合并式更新**:标签采用合并逻辑处理 - 现有标签会保留,除非被显式更新或删除
+2. **数组格式**:标签使用数组格式 `[{"key": "key1", "value": "value1"}]`
+3. **键过滤**:空键会被自动过滤并被验证拒绝
+4. **空值删除**:设置 `value: ""` (空字符串)可删除现有标签键
+5. **添加/更新逻辑**:非空值会添加新标签或更新现有标签
+6. **唯一性检查**:同一标签操作中不允许重复的键名
+7. **限制**:最多50个标签,键名长度≤100字符,值长度≤500字符
+8. **持久化**:所有标签操作自动保存到磁盘,重启后恢复
+
## 实例数据结构
API响应中的实例对象包含以下字段:
@@ -792,7 +875,13 @@ API响应中的实例对象包含以下字段:
"type": "server", // 实例类型:server 或 client
"status": "running", // 实例状态:running、stopped 或 error
"url": "server://...", // 实例配置URL
+ "config": "server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0", // 完整配置URL
"restart": true, // 自启动策略
+ "tags": [ // 标签数组
+ {"key": "environment", "value": "production"},
+ {"key": "project", "value": "web-service"},
+ {"key": "region", "value": "us-west-2"}
+ ],
"mode": 0, // 运行模式
"tcprx": 1024, // TCP接收字节数
"tcptx": 2048, // TCP发送字节数
@@ -803,8 +892,31 @@ API响应中的实例对象包含以下字段:
**注意:**
- `alias` 字段为可选,如果未设置则为空字符串
+- `config` 字段包含实例的完整配置URL,由系统自动生成
- `mode` 字段表示实例当前的运行模式
- `restart` 字段控制实例的自启动行为
+- `tags` 字段为可选,仅在设置标签时存在
+
+### 实例配置字段
+
+NodePass主控会自动为每个实例维护 `config` 字段:
+
+- **自动生成**:在实例创建和更新时自动生成,无需手动维护
+- **完整配置**:包含实例的完整URL,带有所有默认参数
+- **配置继承**:log和tls配置继承自主控设置
+- **默认参数**:其他参数使用系统默认值
+- **只读性质**:自动生成的字段,通过API无法直接修改
+
+**示例 config 字段值:**
+```
+server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0
+```
+
+此功能特别适用于:
+- 配置备份和导出
+- 实例配置的完整性检查
+- 自动化部署脚本
+- 配置文档生成
## 系统信息端点
@@ -824,13 +936,14 @@ GET /info
```json
{
+ "alias": "dev", // 主控别名
"os": "linux", // 操作系统类型
"arch": "amd64", // 系统架构
"cpu": 45, // CPU使用率百分比(仅Linux系统)
"mem_total": 8589934592, // 内存容量(字节,仅Linux系统)
- "mem_free": 2684354560, // 内存可用(字节,仅Linux系统)
+ "mem_used": 2684354560, // 内存已用(字节,仅Linux系统)
"swap_total": 3555328000, // 交换区总量(字节,仅Linux系统)
- "swap_free": 3555328000, // 交换区可用(字节,仅Linux系统)
+ "swap_used": 3555328000, // 交换区已用(字节,仅Linux系统)
"netrx": 1048576000, // 网络接收字节数(累计值,仅Linux)
"nettx": 2097152000, // 网络发送字节数(累计值,仅Linux)
"diskr": 4194304000, // 磁盘读取字节数(累计值,仅Linux)
@@ -878,12 +991,16 @@ function displaySystemStatus() {
console.log(`CPU使用率: ${info.cpu}%`);
}
if (info.mem_total > 0) {
- const memUsagePercent = ((info.mem_total - info.mem_free) / info.mem_total * 100).toFixed(1);
- console.log(`内存使用率: ${memUsagePercent}% (${(info.mem_free / 1024 / 1024 / 1024).toFixed(1)}GB 可用,共 ${(info.mem_total / 1024 / 1024 / 1024).toFixed(1)}GB)`);
+ const memUsagePercent = (info.mem_used / info.mem_total * 100).toFixed(1);
+ const memFreeGB = ((info.mem_total - info.mem_used) / 1024 / 1024 / 1024).toFixed(1);
+ const memTotalGB = (info.mem_total / 1024 / 1024 / 1024).toFixed(1);
+ console.log(`内存使用率: ${memUsagePercent}% (${memFreeGB}GB 可用,共 ${memTotalGB}GB)`);
}
if (info.swap_total > 0) {
- const swapUsagePercent = ((info.swap_total - info.swap_free) / info.swap_total * 100).toFixed(1);
- console.log(`交换区使用率: ${swapUsagePercent}% (${(info.swap_free / 1024 / 1024 / 1024).toFixed(1)}GB 可用,共 ${(info.swap_total / 1024 / 1024 / 1024).toFixed(1)}GB)`);
+ const swapUsagePercent = (info.swap_used / info.swap_total * 100).toFixed(1);
+ const swapFreeGB = ((info.swap_total - info.swap_used) / 1024 / 1024 / 1024).toFixed(1);
+ const swapTotalGB = (info.swap_total / 1024 / 1024 / 1024).toFixed(1);
+ console.log(`交换区使用率: ${swapUsagePercent}% (${swapFreeGB}GB 可用,共 ${swapTotalGB}GB)`);
}
} else {
console.log('CPU、内存、交换区、网络I/O、磁盘I/O和系统运行时间监控功能仅在Linux系统上可用');
@@ -909,8 +1026,8 @@ function displaySystemStatus() {
- **日志级别验证**:确认当前日志级别符合预期
- **资源监控**:在Linux系统上,监控CPU、内存、交换区、网络I/O、磁盘I/O使用情况以确保最佳性能
- CPU使用率通过解析`/proc/stat`计算(非空闲时间百分比)
- - 内存信息通过解析`/proc/meminfo`获取(总量和可用量,单位为字节)
- - 交换区信息通过解析`/proc/meminfo`获取(总量和可用量,单位为字节)
+ - 内存信息通过解析`/proc/meminfo`获取(总量和已用量,单位为字节,已用量计算为总量减去可用量)
+ - 交换区信息通过解析`/proc/meminfo`获取(总量和已用量,单位为字节,已用量计算为总量减去空闲量)
- 网络I/O通过解析`/proc/net/dev`计算(累计字节数,排除虚拟接口)
- 磁盘I/O通过解析`/proc/diskstats`计算(累计字节数,仅统计主设备)
- 系统运行时间通过解析`/proc/uptime`获取
@@ -986,13 +1103,12 @@ const instance = await fetch(`${API_URL}/instances/abc123`, {
```
#### PATCH /instances/{id}
-- **描述**:更新实例状态、别名或执行控制操作
+- **描述**:更新实例状态、别名、标签或执行控制操作
- **认证**:需要API Key
-- **请求体**:`{ "alias": "新别名", "action": "start|stop|restart|reset", "restart": true|false }`
-- **特点**:不中断正在运行的实例,仅更新指定字段。`action: "reset"` 可将该实例的流量统计(tcprx、tcptx、udprx、udptx)清零。
+- **请求体**:`{ "alias": "新别名", "action": "start|stop|restart|reset", "restart": true|false, "tags": [{"key": "键", "value": "值"}] }`
- **示例**:
```javascript
-// 更新别名和自启动策略
+// 更新标签
await fetch(`${API_URL}/instances/abc123`, {
method: 'PATCH',
headers: {
@@ -1000,29 +1116,27 @@ await fetch(`${API_URL}/instances/abc123`, {
'X-API-Key': apiKey
},
body: JSON.stringify({
- alias: 'Web服务器',
- restart: true
+ tags: [
+ {"key": "environment", "value": "production"},
+ {"key": "region", "value": "us-west-2"}
+ ]
})
});
-// 控制实例操作
+// 一次操作中更新和删除标签
await fetch(`${API_URL}/instances/abc123`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
- body: JSON.stringify({ action: 'restart' })
-});
-
-// 清零流量统计
-await fetch(`${API_URL}/instances/abc123`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'X-API-Key': apiKey
- },
- body: JSON.stringify({ action: 'reset' })
+ body: JSON.stringify({
+ tags: [
+ {"key": "environment", "value": "staging"}, // 更新现有
+ {"key": "version", "value": "2.0"}, // 添加新的
+ {"key": "old-tag", "value": ""} // 删除现有
+ ]
+ })
});
```
@@ -1073,6 +1187,28 @@ await fetch(`${API_URL}/instances/abc123`, {
- **认证**:需要API Key
- **响应**:包含系统信息、版本、运行时间、CPU和RAM使用率等
+#### POST /info
+- **描述**:更新主控别名
+- **认证**:需要API Key
+- **请求体**:`{ "alias": "新别名" }`
+- **响应**:完整的主控信息(与GET /info相同)
+- **示例**:
+```javascript
+// 更新主控别名
+const response = await fetch(`${API_URL}/info`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-API-Key': apiKey
+ },
+ body: JSON.stringify({ alias: '我的NodePass服务器' })
+});
+
+const data = await response.json();
+console.log('更新后的别名:', data.alias);
+// 响应包含完整的系统信息,包括更新后的别名
+```
+
#### GET /tcping
- **描述**:TCP连接测试,检测目标地址的连通性和延迟
- **认证**:需要API Key
@@ -1129,9 +1265,10 @@ client://:/:?
| `tls` | TLS加密级别 | `0`(无), `1`(自签名), `2`(证书) | `0` | 仅服务器 |
| `crt` | 证书路径 | 文件路径 | 无 | 仅服务器 |
| `key` | 私钥路径 | 文件路径 | 无 | 仅服务器 |
-| `mode` | 运行模式控制 | `0`(自动), `1`(强制模式1), `2`(强制模式2) | `0` | 两者 |
| `min` | 最小连接池容量 | 整数 > 0 | `64` | 仅客户端双端握手模式 |
| `max` | 最大连接池容量 | 整数 > 0 | `1024` | 双端握手模式 |
+| `mode` | 运行模式控制 | `0`(自动), `1`(强制模式1), `2`(强制模式2) | `0` | 两者 |
| `read` | 读取超时时间 | 时间长度 (如 `10m`, `30s`, `1h`) | `10m` | 两者 |
| `rate` | 带宽速率限制 | 整数 (Mbps), 0=无限制 | `0` | 两者 |
-| `proxy` | PROXY协议支持 | `0`(禁用), `1`(启用) | `0` | 两者 |
+| `slot` | 连接槽位数 | 整数 (1-65536) | `65536` | 两者 |
+| `proxy` | PROXY协议支持 | `0`(禁用), `1`(启用) | `0` | 两者 |
\ No newline at end of file
diff --git a/nodepass/docs/zh/configuration.md b/nodepass/docs/zh/configuration.md
index 3117aaa22d..40c34f3fa5 100644
--- a/nodepass/docs/zh/configuration.md
+++ b/nodepass/docs/zh/configuration.md
@@ -254,19 +254,19 @@ nodepass "server://0.0.0.0:10101/0.0.0.0:8080?log=info&tls=1&proxy=1&rate=100"
NodePass支持通过URL查询参数进行灵活配置,不同参数在 server、client、master 模式下的适用性如下表:
-| 参数 | 说明 | server | client | master |
-|-----------|----------------------|:------:|:------:|:------:|
-| `log` | 日志级别 | O | O | O |
-| `tls` | TLS加密模式 | O | X | O |
-| `crt` | 自定义证书路径 | O | X | O |
-| `key` | 自定义密钥路径 | O | X | O |
-| `min` | 最小连接池容量 | X | O | X |
-| `max` | 最大连接池容量 | O | X | X |
-| `mode` | 运行模式控制 | O | O | X |
-| `read` | 读取超时时间 | O | O | X |
-| `rate` | 带宽速率限制 | O | O | X |
-| `slot` | 最大连接数限制 | O | O | X |
-| `proxy` | PROXY协议支持 | O | O | X |
+| 参数 | 说明 | 默认值 | server | client | master |
+|-----------|----------------------|-----------|:------:|:------:|:------:|
+| `log` | 日志级别 | `info` | O | O | O |
+| `tls` | TLS加密模式 | `0` | O | X | O |
+| `crt` | 自定义证书路径 | N/A | O | X | O |
+| `key` | 自定义密钥路径 | N/A | O | X | O |
+| `min` | 最小连接池容量 | `64` | X | O | X |
+| `max` | 最大连接池容量 | `1024` | O | X | X |
+| `mode` | 运行模式控制 | `0` | O | O | X |
+| `read` | 读取超时时间 | `1h` | O | O | X |
+| `rate` | 带宽速率限制 | `0` | O | O | X |
+| `slot` | 最大连接数限制 | `65536` | O | O | X |
+| `proxy` | PROXY协议支持 | `0` | O | O | X |
- O:参数有效,推荐根据实际场景配置
@@ -286,6 +286,7 @@ NodePass支持通过URL查询参数进行灵活配置,不同参数在 server
| 变量 | 描述 | 默认值 | 示例 |
|----------|-------------|---------|---------|
| `NP_SEMAPHORE_LIMIT` | 信号缓冲区大小 | 65536 | `export NP_SEMAPHORE_LIMIT=2048` |
+| `NP_TCP_DATA_BUF_SIZE` | TCP数据传输缓冲区大小 | 32768 | `export NP_TCP_DATA_BUF_SIZE=65536` |
| `NP_UDP_DATA_BUF_SIZE` | UDP数据包缓冲区大小 | 2048 | `export NP_UDP_DATA_BUF_SIZE=16384` |
| `NP_HANDSHAKE_TIMEOUT` | 握手操作超时 | 10s | `export NP_HANDSHAKE_TIMEOUT=30s` |
| `NP_TCP_DIAL_TIMEOUT` | TCP连接建立超时 | 30s | `export NP_TCP_DIAL_TIMEOUT=60s` |
@@ -349,6 +350,11 @@ NodePass支持通过URL查询参数进行灵活配置,不同参数在 server
对于TCP连接的优化:
+- `NP_TCP_DATA_BUF_SIZE`:TCP数据传输缓冲区大小
+ - 默认值(32768)为大多数应用提供良好平衡
+ - 对于需要大缓冲区的高吞吐量应用增加此值
+ - 考虑为批量数据传输和流媒体增加到65536或更高
+
- `NP_TCP_DIAL_TIMEOUT`:TCP连接建立超时
- 默认值(30s)适用于大多数网络条件
- 对于网络条件不稳定的环境增加此值
@@ -402,6 +408,7 @@ nodepass "client://server.example.com:10101/127.0.0.1:8080?min=128&rate=500&slot
export NP_MIN_POOL_INTERVAL=50ms
export NP_MAX_POOL_INTERVAL=500ms
export NP_SEMAPHORE_LIMIT=8192
+export NP_TCP_DATA_BUF_SIZE=65536
export NP_UDP_DATA_BUF_SIZE=32768
export NP_POOL_GET_TIMEOUT=60s
export NP_REPORT_INTERVAL=10s
diff --git a/nodepass/docs/zh/usage.md b/nodepass/docs/zh/usage.md
index a5acbd0da1..f60edeba3d 100644
--- a/nodepass/docs/zh/usage.md
+++ b/nodepass/docs/zh/usage.md
@@ -330,7 +330,7 @@ NodePass使用隧道密钥来验证客户端和服务端之间的连接。密钥
客户端与服务端的握手过程如下:
1. **客户端连接**:客户端连接到服务端的隧道地址
-2. **密钥验证**:客户端发送XOR加密的隧道密钥
+2. **密钥验证**:客户端发送加密的隧道密钥
3. **服务端验证**:服务端解密并验证密钥是否匹配
4. **配置同步**:验证成功后,服务端发送隧道配置信息,包括:
- 数据流向模式
diff --git a/nodepass/go.mod b/nodepass/go.mod
index 6eadee1c25..8b8584acfe 100644
--- a/nodepass/go.mod
+++ b/nodepass/go.mod
@@ -4,7 +4,7 @@ go 1.25.0
require (
github.com/NodePassProject/cert v1.0.1
- github.com/NodePassProject/conn v1.0.12
+ github.com/NodePassProject/conn v1.0.15
github.com/NodePassProject/logs v1.0.3
- github.com/NodePassProject/pool v1.0.24
+ github.com/NodePassProject/pool v1.0.30
)
diff --git a/nodepass/go.sum b/nodepass/go.sum
index 7068e10bf9..9da59339ed 100644
--- a/nodepass/go.sum
+++ b/nodepass/go.sum
@@ -1,8 +1,8 @@
github.com/NodePassProject/cert v1.0.1 h1:BDy2tTOudy6yk7hvcmScAJMw4NrpCdSCsbuu7hHsIuw=
github.com/NodePassProject/cert v1.0.1/go.mod h1:wP7joOJeQAIlIuOUmhHPwMExjuwGa4XApMWQYChGSrk=
-github.com/NodePassProject/conn v1.0.12 h1:63Ueb//leEptKNU8MXlrEDF3exJiitZefmJAEyMLnt4=
-github.com/NodePassProject/conn v1.0.12/go.mod h1:xfQ7ZLUxrtdLsljGHYYCToW+Hdg6DAbmL1Cs94n5h6E=
+github.com/NodePassProject/conn v1.0.15 h1:YJaWphxGO4EZGdel/Lw4taLcb2hmHefjJipkilHn0B4=
+github.com/NodePassProject/conn v1.0.15/go.mod h1:xfQ7ZLUxrtdLsljGHYYCToW+Hdg6DAbmL1Cs94n5h6E=
github.com/NodePassProject/logs v1.0.3 h1:CDUZVQ477vmmFQHazrQCWM0gJPNINm0C2N3FzC4jVyw=
github.com/NodePassProject/logs v1.0.3/go.mod h1:TwtPXOzLtb8iH+fdduQjEEywICXivsM39cy9AinMSks=
-github.com/NodePassProject/pool v1.0.24 h1:8DSgZ2dxnzYXgplp9ZwZlpwFiKIWU4JYW1glUm+hq/4=
-github.com/NodePassProject/pool v1.0.24/go.mod h1:joQFk1oocg56QpJ1QK/2g5Jv/AyqYUQgPXMG1gWe8iA=
+github.com/NodePassProject/pool v1.0.30 h1:EupeGn6nTOCzybQMHWmEphPjR7CTEptgOFvkD0UMw6Q=
+github.com/NodePassProject/pool v1.0.30/go.mod h1:joQFk1oocg56QpJ1QK/2g5Jv/AyqYUQgPXMG1gWe8iA=
diff --git a/nodepass/internal/client.go b/nodepass/internal/client.go
index e839e619de..2c37719163 100644
--- a/nodepass/internal/client.go
+++ b/nodepass/internal/client.go
@@ -3,7 +3,6 @@ package internal
import (
"bufio"
- "bytes"
"context"
"fmt"
"io"
@@ -13,6 +12,7 @@ import (
"os/signal"
"strconv"
"strings"
+ "sync"
"syscall"
"time"
@@ -28,25 +28,39 @@ type Client struct {
}
// NewClient 创建新的客户端实例
-func NewClient(parsedURL *url.URL, logger *logs.Logger) *Client {
+func NewClient(parsedURL *url.URL, logger *logs.Logger) (*Client, error) {
client := &Client{
Common: Common{
logger: logger,
signalChan: make(chan string, semaphoreLimit),
+ tcpBufferPool: &sync.Pool{
+ New: func() any {
+ buf := make([]byte, tcpDataBufSize)
+ return &buf
+ },
+ },
+ udpBufferPool: &sync.Pool{
+ New: func() any {
+ buf := make([]byte, udpDataBufSize)
+ return &buf
+ },
+ },
},
tunnelName: parsedURL.Hostname(),
}
- client.initConfig(parsedURL)
+ if err := client.initConfig(parsedURL); err != nil {
+ return nil, fmt.Errorf("newClient: initConfig failed: %w", err)
+ }
client.initRateLimiter()
- return client
+ return client, nil
}
// Run 管理客户端生命周期
func (c *Client) Run() {
logInfo := func(prefix string) {
- c.logger.Info("%v: %v@%v/%v?min=%v&mode=%v&read=%v&rate=%v&slot=%v",
+ c.logger.Info("%v: client://%v@%v/%v?min=%v&mode=%v&read=%v&rate=%v&slot=%v&proxy=%v",
prefix, c.tunnelKey, c.tunnelTCPAddr, c.targetTCPAddr,
- c.minPoolCapacity, c.runMode, c.readTimeout, c.rateLimit/125000, c.slotLimit)
+ c.minPoolCapacity, c.runMode, c.readTimeout, c.rateLimit/125000, c.slotLimit, c.proxyProtocol)
}
logInfo("Client started")
@@ -168,7 +182,7 @@ func (c *Client) tunnelHandshake() error {
c.tunnelTCPConn.SetKeepAlivePeriod(reportInterval)
// 发送隧道密钥
- _, err = c.tunnelTCPConn.Write(append(c.xor([]byte(c.tunnelKey)), '\n'))
+ _, err = c.tunnelTCPConn.Write(c.encode([]byte(c.tunnelKey)))
if err != nil {
return fmt.Errorf("tunnelHandshake: write tunnel key failed: %w", err)
}
@@ -179,8 +193,14 @@ func (c *Client) tunnelHandshake() error {
return fmt.Errorf("tunnelHandshake: readBytes failed: %w", err)
}
+ // 解码隧道URL
+ tunnelURLData, err := c.decode(rawTunnelURL)
+ if err != nil {
+ return fmt.Errorf("tunnelHandshake: decode tunnel URL failed: %w", err)
+ }
+
// 解析隧道URL
- tunnelURL, err := url.Parse(string(c.xor(bytes.TrimSuffix(rawTunnelURL, []byte{'\n'}))))
+ tunnelURL, err := url.Parse(string(tunnelURLData))
if err != nil {
return fmt.Errorf("tunnelHandshake: parse tunnel URL failed: %w", err)
}
diff --git a/nodepass/internal/common.go b/nodepass/internal/common.go
index 680e049c97..ae5f45fef0 100644
--- a/nodepass/internal/common.go
+++ b/nodepass/internal/common.go
@@ -5,6 +5,7 @@ import (
"bufio"
"bytes"
"context"
+ "encoding/base64"
"encoding/hex"
"fmt"
"hash/fnv"
@@ -48,6 +49,8 @@ type Common struct {
rateLimiter *conn.RateLimiter // 全局限速器
readTimeout time.Duration // 读取超时
bufReader *bufio.Reader // 缓冲读取器
+ tcpBufferPool *sync.Pool // TCP缓冲区池
+ udpBufferPool *sync.Pool // UDP缓冲区池
signalChan chan string // 信号通道
checkPoint time.Time // 检查点时间
slotLimit int32 // 槽位限制
@@ -64,6 +67,7 @@ type Common struct {
// 配置变量,可通过环境变量调整
var (
semaphoreLimit = getEnvAsInt("NP_SEMAPHORE_LIMIT", 65536) // 信号量限制
+ tcpDataBufSize = getEnvAsInt("NP_TCP_DATA_BUF_SIZE", 32768) // TCP缓冲区大小
udpDataBufSize = getEnvAsInt("NP_UDP_DATA_BUF_SIZE", 2048) // UDP缓冲区大小
handshakeTimeout = getEnvAsDuration("NP_HANDSHAKE_TIMEOUT", 10*time.Second) // 握手超时
tcpDialTimeout = getEnvAsDuration("NP_TCP_DIAL_TIMEOUT", 30*time.Second) // TCP拨号超时
@@ -77,28 +81,40 @@ var (
ReloadInterval = getEnvAsDuration("NP_RELOAD_INTERVAL", 1*time.Hour) // 重载间隔
)
-// UDP缓冲区池
-var udpBufferPool = sync.Pool{
- New: func() any {
- b := make([]byte, udpDataBufSize)
- return &b
- },
+// 默认配置
+const (
+ defaultMinPool = 64 // 默认最小池容量
+ defaultMaxPool = 1024 // 默认最大池容量
+ defaultRunMode = "0" // 默认运行模式
+ defaultReadTimeout = 1 * time.Hour // 默认读取超时
+ defaultRateLimit = 0 // 默认速率限制
+ defaultSlotLimit = 65536 // 默认槽位限制
+ defaultProxyProtocol = "0" // 默认代理协议
+)
+
+// getTCPBuffer 获取TCP缓冲区
+func (c *Common) getTCPBuffer() []byte {
+ buf := c.tcpBufferPool.Get().(*[]byte)
+ return (*buf)[:tcpDataBufSize]
}
-// getUDPBuffer 从池中获取UDP缓冲区
-func getUDPBuffer() []byte {
- buf := udpBufferPool.Get().(*[]byte)
- if cap(*buf) < udpDataBufSize {
- b := make([]byte, udpDataBufSize)
- return b
+// putTCPBuffer 归还TCP缓冲区
+func (c *Common) putTCPBuffer(buf []byte) {
+ if buf != nil && cap(buf) >= tcpDataBufSize {
+ c.tcpBufferPool.Put(&buf)
}
+}
+
+// getUDPBuffer 获取UDP缓冲区
+func (c *Common) getUDPBuffer() []byte {
+ buf := c.udpBufferPool.Get().(*[]byte)
return (*buf)[:udpDataBufSize]
}
-// putUDPBuffer 将UDP缓冲区归还到池中
-func putUDPBuffer(buf []byte) {
- if buf != nil {
- udpBufferPool.Put(&buf)
+// putUDPBuffer 归还UDP缓冲区
+func (c *Common) putUDPBuffer(buf []byte) {
+ if buf != nil && cap(buf) >= udpDataBufSize {
+ c.udpBufferPool.Put(&buf)
}
}
@@ -166,6 +182,59 @@ func (c *Common) xor(data []byte) []byte {
return data
}
+// encode base64编码数据
+func (c *Common) encode(data []byte) []byte {
+ return append([]byte(base64.StdEncoding.EncodeToString(c.xor(data))), '\n')
+}
+
+// decode base64解码数据
+func (c *Common) decode(data []byte) ([]byte, error) {
+ decoded, err := base64.StdEncoding.DecodeString(string(bytes.TrimSuffix(data, []byte{'\n'})))
+ if err != nil {
+ return nil, fmt.Errorf("decode: base64 decode failed: %w", err)
+ }
+ return c.xor(decoded), nil
+}
+
+// getAddress 解析和设置地址信息
+func (c *Common) getAddress(parsedURL *url.URL) error {
+ // 解析隧道地址
+ tunnelAddr := parsedURL.Host
+
+ // 解析隧道TCP地址
+ if tunnelTCPAddr, err := net.ResolveTCPAddr("tcp", tunnelAddr); err == nil {
+ c.tunnelTCPAddr = tunnelTCPAddr
+ } else {
+ return fmt.Errorf("getAddress: resolveTCPAddr failed: %w", err)
+ }
+
+ // 解析隧道UDP地址
+ if tunnelUDPAddr, err := net.ResolveUDPAddr("udp", tunnelAddr); err == nil {
+ c.tunnelUDPAddr = tunnelUDPAddr
+ } else {
+ return fmt.Errorf("getAddress: resolveUDPAddr failed: %w", err)
+ }
+
+ // 处理目标地址
+ targetAddr := strings.TrimPrefix(parsedURL.Path, "/")
+
+ // 解析目标TCP地址
+ if targetTCPAddr, err := net.ResolveTCPAddr("tcp", targetAddr); err == nil {
+ c.targetTCPAddr = targetTCPAddr
+ } else {
+ return fmt.Errorf("getAddress: resolveTCPAddr failed: %w", err)
+ }
+
+ // 解析目标UDP地址
+ if targetUDPAddr, err := net.ResolveUDPAddr("udp", targetAddr); err == nil {
+ c.targetUDPAddr = targetUDPAddr
+ } else {
+ return fmt.Errorf("getAddress: resolveUDPAddr failed: %w", err)
+ }
+
+ return nil
+}
+
// getTunnelKey 从URL中获取隧道密钥
func (c *Common) getTunnelKey(parsedURL *url.URL) {
if key := parsedURL.User.Username(); key != "" {
@@ -177,43 +246,6 @@ func (c *Common) getTunnelKey(parsedURL *url.URL) {
}
}
-// getAddress 解析和设置地址信息
-func (c *Common) getAddress(parsedURL *url.URL) {
- // 解析隧道地址
- tunnelAddr := parsedURL.Host
-
- // 解析隧道TCP地址
- if tunnelTCPAddr, err := net.ResolveTCPAddr("tcp", tunnelAddr); err == nil {
- c.tunnelTCPAddr = tunnelTCPAddr
- } else {
- c.logger.Error("getAddress: resolveTCPAddr failed: %v", err)
- }
-
- // 解析隧道UDP地址
- if tunnelUDPAddr, err := net.ResolveUDPAddr("udp", tunnelAddr); err == nil {
- c.tunnelUDPAddr = tunnelUDPAddr
- } else {
- c.logger.Error("getAddress: resolveUDPAddr failed: %v", err)
- }
-
- // 处理目标地址
- targetAddr := strings.TrimPrefix(parsedURL.Path, "/")
-
- // 解析目标TCP地址
- if targetTCPAddr, err := net.ResolveTCPAddr("tcp", targetAddr); err == nil {
- c.targetTCPAddr = targetTCPAddr
- } else {
- c.logger.Error("getAddress: resolveTCPAddr failed: %v", err)
- }
-
- // 解析目标UDP地址
- if targetUDPAddr, err := net.ResolveUDPAddr("udp", targetAddr); err == nil {
- c.targetUDPAddr = targetUDPAddr
- } else {
- c.logger.Error("getAddress: resolveUDPAddr failed: %v", err)
- }
-}
-
// getPoolCapacity 获取连接池容量设置
func (c *Common) getPoolCapacity(parsedURL *url.URL) {
if min := parsedURL.Query().Get("min"); min != "" {
@@ -221,7 +253,7 @@ func (c *Common) getPoolCapacity(parsedURL *url.URL) {
c.minPoolCapacity = value
}
} else {
- c.minPoolCapacity = 64
+ c.minPoolCapacity = defaultMinPool
}
if max := parsedURL.Query().Get("max"); max != "" {
@@ -229,7 +261,16 @@ func (c *Common) getPoolCapacity(parsedURL *url.URL) {
c.maxPoolCapacity = value
}
} else {
- c.maxPoolCapacity = 1024
+ c.maxPoolCapacity = defaultMaxPool
+ }
+}
+
+// getRunMode 获取运行模式
+func (c *Common) getRunMode(parsedURL *url.URL) {
+ if mode := parsedURL.Query().Get("mode"); mode != "" {
+ c.runMode = mode
+ } else {
+ c.runMode = defaultRunMode
}
}
@@ -240,16 +281,7 @@ func (c *Common) getReadTimeout(parsedURL *url.URL) {
c.readTimeout = value
}
} else {
- c.readTimeout = 1 * time.Hour
- }
-}
-
-// getRunMode 获取运行模式
-func (c *Common) getRunMode(parsedURL *url.URL) {
- if mode := parsedURL.Query().Get("mode"); mode != "" {
- c.runMode = mode
- } else {
- c.runMode = "0"
+ c.readTimeout = defaultReadTimeout
}
}
@@ -260,7 +292,7 @@ func (c *Common) getRateLimit(parsedURL *url.URL) {
c.rateLimit = value * 125000
}
} else {
- c.rateLimit = 0
+ c.rateLimit = defaultRateLimit
}
}
@@ -271,7 +303,7 @@ func (c *Common) getSlotLimit(parsedURL *url.URL) {
c.slotLimit = int32(value)
}
} else {
- c.slotLimit = 65536
+ c.slotLimit = defaultSlotLimit
}
}
@@ -280,20 +312,25 @@ func (c *Common) getProxyProtocol(parsedURL *url.URL) {
if protocol := parsedURL.Query().Get("proxy"); protocol != "" {
c.proxyProtocol = protocol
} else {
- c.proxyProtocol = "0"
+ c.proxyProtocol = defaultProxyProtocol
}
}
// initConfig 初始化配置
-func (c *Common) initConfig(parsedURL *url.URL) {
+func (c *Common) initConfig(parsedURL *url.URL) error {
+ if err := c.getAddress(parsedURL); err != nil {
+ return err
+ }
+
c.getTunnelKey(parsedURL)
- c.getAddress(parsedURL)
c.getPoolCapacity(parsedURL)
- c.getReadTimeout(parsedURL)
c.getRunMode(parsedURL)
+ c.getReadTimeout(parsedURL)
c.getRateLimit(parsedURL)
c.getSlotLimit(parsedURL)
c.getProxyProtocol(parsedURL)
+
+ return nil
}
// sendProxyV1Header 发送PROXY v1
@@ -517,7 +554,19 @@ func (c *Common) commonQueue() error {
if err != nil {
return fmt.Errorf("commonQueue: readBytes failed: %w", err)
}
- signal := string(c.xor(bytes.TrimSuffix(rawSignal, []byte{'\n'})))
+
+ // 解码信号
+ signalData, err := c.decode(rawSignal)
+ if err != nil {
+ c.logger.Error("commonQueue: decode signal failed: %v", err)
+ select {
+ case <-c.ctx.Done():
+ return fmt.Errorf("commonQueue: context error: %w", c.ctx.Err())
+ case <-time.After(50 * time.Millisecond):
+ }
+ continue
+ }
+ signal := string(signalData)
// 将信号发送到通道
select {
@@ -550,7 +599,7 @@ func (c *Common) healthCheck() error {
// 连接池健康度检查
if c.tunnelPool.ErrorCount() > c.tunnelPool.Active()/2 {
// 发送刷新信号到对端
- _, err := c.tunnelTCPConn.Write(append(c.xor([]byte(flushURL.String())), '\n'))
+ _, err := c.tunnelTCPConn.Write(c.encode([]byte(flushURL.String())))
if err != nil {
c.mu.Unlock()
return fmt.Errorf("healthCheck: write flush signal failed: %w", err)
@@ -569,7 +618,7 @@ func (c *Common) healthCheck() error {
// 发送PING信号
c.checkPoint = time.Now()
- _, err := c.tunnelTCPConn.Write(append(c.xor([]byte(pingURL.String())), '\n'))
+ _, err := c.tunnelTCPConn.Write(c.encode([]byte(pingURL.String())))
if err != nil {
c.mu.Unlock()
return fmt.Errorf("healthCheck: write ping signal failed: %w", err)
@@ -674,7 +723,7 @@ func (c *Common) commonTCPLoop() {
}
c.mu.Lock()
- _, err = c.tunnelTCPConn.Write(append(c.xor([]byte(launchURL.String())), '\n'))
+ _, err = c.tunnelTCPConn.Write(c.encode([]byte(launchURL.String())))
c.mu.Unlock()
if err != nil {
@@ -684,9 +733,16 @@ func (c *Common) commonTCPLoop() {
c.logger.Debug("TCP launch signal: cid %v -> %v", id, c.tunnelTCPConn.RemoteAddr())
+ buffer1 := c.getTCPBuffer()
+ buffer2 := c.getTCPBuffer()
+ defer func() {
+ c.putTCPBuffer(buffer1)
+ c.putTCPBuffer(buffer2)
+ }()
+
// 交换数据
c.logger.Debug("Starting exchange: %v <-> %v", remoteConn.LocalAddr(), targetConn.LocalAddr())
- c.logger.Debug("Exchange complete: %v", conn.DataExchange(remoteConn, targetConn, c.readTimeout))
+ c.logger.Debug("Exchange complete: %v", conn.DataExchange(remoteConn, targetConn, c.readTimeout, buffer1, buffer2))
}(targetConn)
}
}
@@ -698,17 +754,17 @@ func (c *Common) commonUDPLoop() {
return
}
- buffer := getUDPBuffer()
+ buffer := c.getUDPBuffer()
// 读取来自目标的UDP数据
x, clientAddr, err := c.targetUDPConn.ReadFromUDP(buffer)
if err != nil {
if c.ctx.Err() != nil || err == net.ErrClosed {
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
return
}
c.logger.Error("commonUDPLoop: readFromUDP failed: %v", err)
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
select {
case <-c.ctx.Done():
@@ -733,7 +789,7 @@ func (c *Common) commonUDPLoop() {
// 尝试获取UDP连接槽位
if !c.tryAcquireSlot(true) {
c.logger.Error("commonUDPLoop: UDP slot limit reached: %v/%v", c.udpSlot, c.slotLimit)
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
continue
}
@@ -760,8 +816,8 @@ func (c *Common) commonUDPLoop() {
c.releaseSlot(true)
}()
- buffer := getUDPBuffer()
- defer putUDPBuffer(buffer)
+ buffer := c.getUDPBuffer()
+ defer c.putUDPBuffer(buffer)
reader := &conn.TimeoutReader{Conn: remoteConn, Timeout: c.readTimeout}
for {
@@ -800,7 +856,7 @@ func (c *Common) commonUDPLoop() {
}
c.mu.Lock()
- _, err = c.tunnelTCPConn.Write(append(c.xor([]byte(launchURL.String())), '\n'))
+ _, err = c.tunnelTCPConn.Write(c.encode([]byte(launchURL.String())))
c.mu.Unlock()
if err != nil {
c.logger.Error("commonUDPLoop: write launch signal failed: %v", err)
@@ -817,13 +873,13 @@ func (c *Common) commonUDPLoop() {
c.logger.Error("commonUDPLoop: write to tunnel failed: %v", err)
c.targetUDPSession.Delete(sessionKey)
remoteConn.Close()
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
continue
}
// 传输完成
c.logger.Debug("Transfer complete: %v <-> %v", remoteConn.LocalAddr(), c.targetUDPConn.LocalAddr())
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
}
}
@@ -878,7 +934,7 @@ func (c *Common) commonOnce() error {
go c.commonUDPOnce(signalURL)
case "i": // PING
c.mu.Lock()
- _, err := c.tunnelTCPConn.Write(append(c.xor([]byte(pongURL.String())), '\n'))
+ _, err := c.tunnelTCPConn.Write(c.encode([]byte(pongURL.String())))
c.mu.Unlock()
if err != nil {
return fmt.Errorf("commonOnce: write pong signal failed: %w", err)
@@ -954,9 +1010,16 @@ func (c *Common) commonTCPOnce(signalURL *url.URL) {
return
}
+ buffer1 := c.getTCPBuffer()
+ buffer2 := c.getTCPBuffer()
+ defer func() {
+ c.putTCPBuffer(buffer1)
+ c.putTCPBuffer(buffer2)
+ }()
+
// 交换数据
c.logger.Debug("Starting exchange: %v <-> %v", remoteConn.LocalAddr(), targetConn.LocalAddr())
- c.logger.Debug("Exchange complete: %v", conn.DataExchange(remoteConn, targetConn, c.readTimeout))
+ c.logger.Debug("Exchange complete: %v", conn.DataExchange(remoteConn, targetConn, c.readTimeout, buffer1, buffer2))
}
// commonUDPOnce 共用处理单个UDP请求
@@ -1013,8 +1076,8 @@ func (c *Common) commonUDPOnce(signalURL *url.URL) {
go func() {
defer func() { done <- struct{}{} }()
- buffer := getUDPBuffer()
- defer putUDPBuffer(buffer)
+ buffer := c.getUDPBuffer()
+ defer c.putUDPBuffer(buffer)
reader := &conn.TimeoutReader{Conn: remoteConn, Timeout: c.readTimeout}
for {
if c.ctx.Err() != nil {
@@ -1047,8 +1110,8 @@ func (c *Common) commonUDPOnce(signalURL *url.URL) {
go func() {
defer func() { done <- struct{}{} }()
- buffer := getUDPBuffer()
- defer putUDPBuffer(buffer)
+ buffer := c.getUDPBuffer()
+ defer c.putUDPBuffer(buffer)
reader := &conn.TimeoutReader{Conn: targetConn, Timeout: c.readTimeout}
for {
if c.ctx.Err() != nil {
@@ -1204,10 +1267,16 @@ func (c *Common) singleTCPLoop() error {
c.logger.Error("singleTCPLoop: sendProxyV1Header failed: %v", err)
return
}
+ buffer1 := c.getTCPBuffer()
+ buffer2 := c.getTCPBuffer()
+ defer func() {
+ c.putTCPBuffer(buffer1)
+ c.putTCPBuffer(buffer2)
+ }()
// 交换数据
c.logger.Debug("Starting exchange: %v <-> %v", tunnelConn.LocalAddr(), targetConn.LocalAddr())
- c.logger.Debug("Exchange complete: %v", conn.DataExchange(tunnelConn, targetConn, c.readTimeout))
+ c.logger.Debug("Exchange complete: %v", conn.DataExchange(tunnelConn, targetConn, c.readTimeout, buffer1, buffer2))
}(tunnelConn)
}
}
@@ -1219,18 +1288,18 @@ func (c *Common) singleUDPLoop() error {
return fmt.Errorf("singleUDPLoop: context error: %w", c.ctx.Err())
}
- buffer := getUDPBuffer()
+ buffer := c.getUDPBuffer()
// 读取来自隧道的UDP数据
x, clientAddr, err := c.tunnelUDPConn.ReadFromUDP(buffer)
if err != nil {
if c.ctx.Err() != nil || err == net.ErrClosed {
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
return fmt.Errorf("singleUDPLoop: context error: %w", c.ctx.Err())
}
c.logger.Error("singleUDPLoop: ReadFromUDP failed: %v", err)
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
select {
case <-c.ctx.Done():
return fmt.Errorf("singleUDPLoop: context error: %w", c.ctx.Err())
@@ -1253,7 +1322,7 @@ func (c *Common) singleUDPLoop() error {
// 尝试获取UDP连接槽位
if !c.tryAcquireSlot(true) {
c.logger.Error("singleUDPLoop: UDP slot limit reached: %v/%v", c.udpSlot, c.slotLimit)
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
continue
}
@@ -1262,7 +1331,7 @@ func (c *Common) singleUDPLoop() error {
if err != nil {
c.logger.Error("singleUDPLoop: dialTimeout failed: %v", err)
c.releaseSlot(true)
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
continue
}
targetConn = newSession
@@ -1277,8 +1346,8 @@ func (c *Common) singleUDPLoop() error {
c.releaseSlot(true)
}()
- buffer := getUDPBuffer()
- defer putUDPBuffer(buffer)
+ buffer := c.getUDPBuffer()
+ defer c.putUDPBuffer(buffer)
reader := &conn.TimeoutReader{Conn: targetConn, Timeout: c.readTimeout}
for {
@@ -1326,12 +1395,12 @@ func (c *Common) singleUDPLoop() error {
if targetConn != nil {
targetConn.Close()
}
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
return fmt.Errorf("singleUDPLoop: write to target failed: %w", err)
}
// 传输完成
c.logger.Debug("Transfer complete: %v <-> %v", targetConn.LocalAddr(), c.tunnelUDPConn.LocalAddr())
- putUDPBuffer(buffer)
+ c.putUDPBuffer(buffer)
}
}
diff --git a/nodepass/internal/master.go b/nodepass/internal/master.go
index ada1790d7a..4dda79b377 100644
--- a/nodepass/internal/master.go
+++ b/nodepass/internal/master.go
@@ -38,6 +38,9 @@ const (
apiKeyID = "********" // API Key的特殊ID
tcpingSemLimit = 10 // TCPing最大并发数
baseDuration = 100 * time.Millisecond // 基准持续时间
+ maxTagsCount = 50 // 最大标签数量
+ maxTagKeyLen = 100 // 标签键最大长度
+ maxTagValueLen = 500 // 标签值最大长度
)
// Swagger UI HTML模板
@@ -64,6 +67,7 @@ const swaggerUIHTML = `
// Master 实现主控模式功能
type Master struct {
Common // 继承通用功能
+ alias string // 主控别名
prefix string // API前缀
version string // NP版本
hostname string // 隧道名称
@@ -80,7 +84,7 @@ type Master struct {
notifyChannel chan *InstanceEvent // 事件通知通道
tcpingSem chan struct{} // TCPing并发控制
startTime time.Time // 启动时间
- backupDone chan struct{} // 备份停止信号
+ periodicDone chan struct{} // 定期任务停止信号
}
// Instance 实例信息
@@ -90,7 +94,9 @@ type Instance struct {
Type string `json:"type"` // 实例类型
Status string `json:"status"` // 实例状态
URL string `json:"url"` // 实例URL
+ Config string `json:"config"` // 实例配置
Restart bool `json:"restart"` // 是否自启动
+ Tags []Tag `json:"tags"` // 标签数组
Mode int32 `json:"mode"` // 实例模式
Ping int32 `json:"ping"` // 端内延迟
Pool int32 `json:"pool"` // 池连接数
@@ -106,25 +112,32 @@ type Instance struct {
UDPTXBase uint64 `json:"-" gob:"-"` // UDP发送字节数基线(不序列化)
cmd *exec.Cmd `json:"-" gob:"-"` // 命令对象(不序列化)
stopped chan struct{} `json:"-" gob:"-"` // 停止信号通道(不序列化)
+ deleted bool `json:"-" gob:"-"` // 删除标志(不序列化)
cancelFunc context.CancelFunc `json:"-" gob:"-"` // 取消函数(不序列化)
lastCheckPoint time.Time `json:"-" gob:"-"` // 上次检查点时间(不序列化)
}
+// Tag 标签结构体
+type Tag struct {
+ Key string `json:"key"` // 标签键
+ Value string `json:"value"` // 标签值
+}
+
// InstanceEvent 实例事件信息
type InstanceEvent struct {
- Type string `json:"type"` // 事件类型:initial, create, update, delete, shutdown, log
- Time time.Time `json:"time"` // 事件时间
- Instance *Instance `json:"instance"` // 关联的实例
- Logs string `json:"logs,omitempty"` // 日志内容,仅当Type为log时有效
+ Type string `json:"type"` // 事件类型:initial, create, update, delete, shutdown, log
+ Time time.Time `json:"time"` // 事件时间
+ Instance *Instance `json:"instance"` // 关联的实例
+ Logs string `json:"logs"` // 日志内容
}
// SystemInfo 系统信息结构体
type SystemInfo struct {
CPU int `json:"cpu"` // CPU使用率 (%)
MemTotal uint64 `json:"mem_total"` // 内存容量字节数
- MemFree uint64 `json:"mem_free"` // 内存可用字节数
+ MemUsed uint64 `json:"mem_used"` // 内存已用字节数
SwapTotal uint64 `json:"swap_total"` // 交换区容量字节数
- SwapFree uint64 `json:"swap_free"` // 交换区可用字节数
+ SwapUsed uint64 `json:"swap_used"` // 交换区已用字节数
NetRX uint64 `json:"netrx"` // 网络接收字节数
NetTX uint64 `json:"nettx"` // 网络发送字节数
DiskR uint64 `json:"diskr"` // 磁盘读取字节数
@@ -191,6 +204,34 @@ func (m *Master) performTCPing(target string) *TCPingResult {
return result
}
+// validateTags 验证标签的有效性
+func validateTags(tags []Tag) error {
+ if len(tags) > maxTagsCount {
+ return fmt.Errorf("too many tags: maximum %d allowed", maxTagsCount)
+ }
+
+ keySet := make(map[string]bool)
+ for _, tag := range tags {
+ if len(tag.Key) == 0 {
+ return fmt.Errorf("tag key cannot be empty")
+ }
+ if len(tag.Key) > maxTagKeyLen {
+ return fmt.Errorf("tag key exceeds maximum length %d", maxTagKeyLen)
+ }
+ if len(tag.Value) > maxTagValueLen {
+ return fmt.Errorf("tag value for key exceeds maximum length %d", maxTagValueLen)
+ }
+
+ // 检查重复的键
+ if keySet[tag.Key] {
+ return fmt.Errorf("duplicate tag key: '%s'", tag.Key)
+ }
+ keySet[tag.Key] = true
+ }
+
+ return nil
+}
+
// InstanceLogWriter 实例日志写入器
type InstanceLogWriter struct {
instanceID string // 实例ID
@@ -246,9 +287,11 @@ func (w *InstanceLogWriter) Write(p []byte) (n int, err error) {
}
w.instance.lastCheckPoint = time.Now()
- w.master.instances.Store(w.instanceID, w.instance)
- // 发送检查点更新事件
- w.master.sendSSEEvent("update", w.instance)
+ // 仅当实例未被删除时才存储和发送更新事件
+ if !w.instance.deleted {
+ w.master.instances.Store(w.instanceID, w.instance)
+ w.master.sendSSEEvent("update", w.instance)
+ }
// 过滤检查点日志
continue
}
@@ -256,8 +299,10 @@ func (w *InstanceLogWriter) Write(p []byte) (n int, err error) {
// 输出日志加实例ID
fmt.Fprintf(w.target, "%s [%s]\n", line, w.instanceID)
- // 发送日志事件
- w.master.sendSSEEvent("log", w.instance, line)
+ // 仅当实例未被删除时才发送日志事件
+ if !w.instance.deleted {
+ w.master.sendSSEEvent("log", w.instance, line)
+ }
}
if err := scanner.Err(); err != nil {
@@ -274,12 +319,11 @@ func setCorsHeaders(w http.ResponseWriter) {
}
// NewMaster 创建新的主控实例
-func NewMaster(parsedURL *url.URL, tlsCode string, tlsConfig *tls.Config, logger *logs.Logger, version string) *Master {
+func NewMaster(parsedURL *url.URL, tlsCode string, tlsConfig *tls.Config, logger *logs.Logger, version string) (*Master, error) {
// 解析主机地址
host, err := net.ResolveTCPAddr("tcp", parsedURL.Host)
if err != nil {
- logger.Error("newMaster: resolveTCPAddr failed: %v", err)
- return nil
+ return nil, fmt.Errorf("newMaster: resolve host failed: %w", err)
}
// 获取隧道名称
@@ -319,7 +363,7 @@ func NewMaster(parsedURL *url.URL, tlsCode string, tlsConfig *tls.Config, logger
notifyChannel: make(chan *InstanceEvent, semaphoreLimit),
tcpingSem: make(chan struct{}, tcpingSemLimit),
startTime: time.Now(),
- backupDone: make(chan struct{}),
+ periodicDone: make(chan struct{}),
}
master.tunnelTCPAddr = host
@@ -329,10 +373,7 @@ func NewMaster(parsedURL *url.URL, tlsCode string, tlsConfig *tls.Config, logger
// 启动事件分发器
go master.startEventDispatcher()
- // 启动定期备份
- go master.startPeriodicBackup()
-
- return master
+ return master, nil
}
// Run 管理主控生命周期
@@ -449,6 +490,9 @@ func (m *Master) Run() {
}
}()
+ // 启动定期任务
+ go m.startPeriodicTasks()
+
// 处理系统信号
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
<-ctx.Done()
@@ -467,53 +511,15 @@ func (m *Master) Run() {
// Shutdown 关闭主控
func (m *Master) Shutdown(ctx context.Context) error {
return m.shutdown(ctx, func() {
- // 声明一个已关闭通道的集合,避免重复关闭
- var closedChannels sync.Map
-
- var wg sync.WaitGroup
-
- // 给所有订阅者一个关闭通知
- m.subscribers.Range(func(key, value any) bool {
- subscriberChan := value.(chan *InstanceEvent)
- wg.Add(1)
- go func(ch chan *InstanceEvent) {
- defer wg.Done()
- // 非阻塞的方式发送关闭事件
- select {
- case ch <- &InstanceEvent{
- Type: "shutdown",
- Time: time.Now(),
- }:
- default:
- // 不可用,忽略
- }
- }(subscriberChan)
- return true
- })
-
- // 等待所有订阅者处理完关闭事件
- time.Sleep(baseDuration)
-
- // 关闭所有订阅者通道
- m.subscribers.Range(func(key, value any) bool {
- subscriberChan := value.(chan *InstanceEvent)
- // 检查通道是否已关闭,如果没有则关闭它
- if _, loaded := closedChannels.LoadOrStore(subscriberChan, true); !loaded {
- wg.Add(1)
- go func(k any, ch chan *InstanceEvent) {
- defer wg.Done()
- close(ch)
- m.subscribers.Delete(k)
- }(key, subscriberChan)
- }
- return true
- })
+ // 通知并关闭SSE连接
+ m.shutdownSSEConnections()
// 停止所有运行中的实例
+ var wg sync.WaitGroup
m.instances.Range(func(key, value any) bool {
instance := value.(*Instance)
- // 如果实例正在运行,则停止它
- if instance.Status == "running" && instance.cmd != nil && instance.cmd.Process != nil {
+ // 如果实例需要停止,则停止它
+ if instance.Status != "stopped" && instance.cmd != nil && instance.cmd.Process != nil {
wg.Add(1)
go func(inst *Instance) {
defer wg.Done()
@@ -525,8 +531,8 @@ func (m *Master) Shutdown(ctx context.Context) error {
wg.Wait()
- // 关闭定期备份
- close(m.backupDone)
+ // 关闭定期任务
+ close(m.periodicDone)
// 关闭事件通知通道,停止事件分发器
close(m.notifyChannel)
@@ -545,6 +551,107 @@ func (m *Master) Shutdown(ctx context.Context) error {
})
}
+// startPeriodicTasks 启动所有定期任务
+func (m *Master) startPeriodicTasks() {
+ go m.startPeriodicBackup()
+ go m.startPeriodicCleanup()
+ go m.startPeriodicRestart()
+}
+
+// startPeriodicBackup 启动定期备份
+func (m *Master) startPeriodicBackup() {
+ for {
+ select {
+ case <-time.After(ReloadInterval):
+ // 固定备份文件名
+ backupPath := fmt.Sprintf("%s.backup", m.statePath)
+
+ if err := m.saveStateToPath(backupPath); err != nil {
+ m.logger.Error("startPeriodicBackup: backup state failed: %v", err)
+ } else {
+ m.logger.Info("State backup saved: %v", backupPath)
+ }
+ case <-m.periodicDone:
+ return
+ }
+ }
+}
+
+// startPeriodicCleanup 启动定期清理重复ID的实例
+func (m *Master) startPeriodicCleanup() {
+ for {
+ select {
+ case <-time.After(reportInterval):
+ // 收集实例并按ID分组
+ idInstances := make(map[string][]*Instance)
+ m.instances.Range(func(key, value any) bool {
+ if id := key.(string); id != apiKeyID {
+ idInstances[id] = append(idInstances[id], value.(*Instance))
+ }
+ return true
+ })
+
+ // 清理重复实例
+ for _, instances := range idInstances {
+ if len(instances) <= 1 {
+ continue
+ }
+
+ // 选择保留实例
+ keepIdx := 0
+ for i, inst := range instances {
+ if inst.Status == "running" && instances[keepIdx].Status != "running" {
+ keepIdx = i
+ }
+ }
+
+ // 清理多余实例
+ for i, inst := range instances {
+ if i == keepIdx {
+ continue
+ }
+ inst.deleted = true
+ if inst.Status != "stopped" {
+ m.stopInstance(inst)
+ }
+ m.instances.Delete(inst.ID)
+ }
+ }
+ case <-m.periodicDone:
+ return
+ }
+ }
+}
+
+// startPeriodicRestart 启动定期错误实例重启
+func (m *Master) startPeriodicRestart() {
+ for {
+ select {
+ case <-time.After(reportInterval):
+ // 收集所有error状态的实例
+ var errorInstances []*Instance
+ m.instances.Range(func(key, value any) bool {
+ if id := key.(string); id != apiKeyID {
+ instance := value.(*Instance)
+ if instance.Status == "error" && !instance.deleted {
+ errorInstances = append(errorInstances, instance)
+ }
+ }
+ return true
+ })
+
+ // 重启所有error状态的实例
+ for _, instance := range errorInstances {
+ m.stopInstance(instance)
+ time.Sleep(baseDuration)
+ m.startInstance(instance)
+ }
+ case <-m.periodicDone:
+ return
+ }
+ }
+}
+
// saveState 保存实例状态到文件
func (m *Master) saveState() error {
return m.saveStateToPath(m.statePath)
@@ -618,27 +725,15 @@ func (m *Master) saveStateToPath(filePath string) error {
return nil
}
-// startPeriodicBackup 启动定期备份
-func (m *Master) startPeriodicBackup() {
- for {
- select {
- case <-time.After(ReloadInterval):
- // 固定备份文件名
- backupPath := fmt.Sprintf("%s.backup", m.statePath)
-
- if err := m.saveStateToPath(backupPath); err != nil {
- m.logger.Error("startPeriodicBackup: backup state failed: %v", err)
- } else {
- m.logger.Info("State backup saved: %v", backupPath)
- }
- case <-m.backupDone:
- return
- }
- }
-}
-
// loadState 从文件加载实例状态
func (m *Master) loadState() {
+ // 清理旧的临时文件
+ if tmpFiles, _ := filepath.Glob(filepath.Join(filepath.Dir(m.statePath), "np-*.tmp")); tmpFiles != nil {
+ for _, f := range tmpFiles {
+ os.Remove(f)
+ }
+ }
+
// 检查文件是否存在
if _, err := os.Stat(m.statePath); os.IsNotExist(err) {
return
@@ -663,6 +758,12 @@ func (m *Master) loadState() {
// 恢复实例
for id, instance := range persistentData {
instance.stopped = make(chan struct{})
+
+ // 生成完整配置
+ if instance.Config == "" && instance.ID != apiKeyID {
+ instance.Config = m.generateConfigURL(instance)
+ }
+
m.instances.Store(id, instance)
// 处理自启动
@@ -679,31 +780,56 @@ func (m *Master) loadState() {
func (m *Master) handleOpenAPISpec(w http.ResponseWriter, r *http.Request) {
setCorsHeaders(w)
w.Header().Set("Content-Type", "application/json")
- w.Write([]byte(generateOpenAPISpec()))
+ w.Write([]byte(m.generateOpenAPISpec()))
}
// handleSwaggerUI 处理Swagger UI请求
func (m *Master) handleSwaggerUI(w http.ResponseWriter, r *http.Request) {
setCorsHeaders(w)
w.Header().Set("Content-Type", "text/html")
- fmt.Fprintf(w, swaggerUIHTML, generateOpenAPISpec())
+ fmt.Fprintf(w, swaggerUIHTML, m.generateOpenAPISpec())
}
// handleInfo 处理系统信息请求
func (m *Master) handleInfo(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodGet {
- httpError(w, "Method not allowed", http.StatusMethodNotAllowed)
- return
- }
+ switch r.Method {
+ case http.MethodGet:
+ writeJSON(w, http.StatusOK, m.getMasterInfo())
+ case http.MethodPost:
+ var reqData struct {
+ Alias string `json:"alias"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
+ httpError(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ // 更新主控别名
+ if len(reqData.Alias) > maxTagKeyLen {
+ httpError(w, fmt.Sprintf("Master alias exceeds maximum length %d", maxTagKeyLen), http.StatusBadRequest)
+ return
+ }
+ m.alias = reqData.Alias
+
+ writeJSON(w, http.StatusOK, m.getMasterInfo())
+
+ default:
+ httpError(w, "Method not allowed", http.StatusMethodNotAllowed)
+ }
+}
+
+// getMasterInfo 获取完整的主控信息
+func (m *Master) getMasterInfo() map[string]any {
info := map[string]any{
+ "alias": m.alias,
"os": runtime.GOOS,
"arch": runtime.GOARCH,
"cpu": -1,
"mem_total": uint64(0),
- "mem_free": uint64(0),
+ "mem_used": uint64(0),
"swap_total": uint64(0),
- "swap_free": uint64(0),
+ "swap_used": uint64(0),
"netrx": uint64(0),
"nettx": uint64(0),
"diskr": uint64(0),
@@ -722,9 +848,9 @@ func (m *Master) handleInfo(w http.ResponseWriter, r *http.Request) {
sysInfo := getLinuxSysInfo()
info["cpu"] = sysInfo.CPU
info["mem_total"] = sysInfo.MemTotal
- info["mem_free"] = sysInfo.MemFree
+ info["mem_used"] = sysInfo.MemUsed
info["swap_total"] = sysInfo.SwapTotal
- info["swap_free"] = sysInfo.SwapFree
+ info["swap_used"] = sysInfo.SwapUsed
info["netrx"] = sysInfo.NetRX
info["nettx"] = sysInfo.NetTX
info["diskr"] = sysInfo.DiskR
@@ -732,7 +858,7 @@ func (m *Master) handleInfo(w http.ResponseWriter, r *http.Request) {
info["sysup"] = sysInfo.SysUp
}
- writeJSON(w, http.StatusOK, info)
+ return info
}
// getLinuxSysInfo 获取Linux系统信息
@@ -740,9 +866,9 @@ func getLinuxSysInfo() SystemInfo {
info := SystemInfo{
CPU: -1,
MemTotal: 0,
- MemFree: 0,
+ MemUsed: 0,
SwapTotal: 0,
- SwapFree: 0,
+ SwapUsed: 0,
NetRX: 0,
NetTX: 0,
DiskR: 0,
@@ -754,7 +880,7 @@ func getLinuxSysInfo() SystemInfo {
return info
}
- // CPU使用率:解析/proc/stat
+ // CPU占用:解析/proc/stat
readStat := func() (idle, total uint64) {
data, err := os.ReadFile("/proc/stat")
if err != nil {
@@ -783,9 +909,9 @@ func getLinuxSysInfo() SystemInfo {
info.CPU = min(int((deltaTotal-deltaIdle)*100/deltaTotal/uint64(numCPU)), 100)
}
- // RAM使用率:解析/proc/meminfo
+ // RAM占用:解析/proc/meminfo
if data, err := os.ReadFile("/proc/meminfo"); err == nil {
- var memTotal, memFree, swapTotal, swapFree uint64
+ var memTotal, memAvailable, swapTotal, swapFree uint64
for line := range strings.SplitSeq(string(data), "\n") {
if fields := strings.Fields(line); len(fields) >= 2 {
if val, err := strconv.ParseUint(fields[1], 10, 64); err == nil {
@@ -793,8 +919,8 @@ func getLinuxSysInfo() SystemInfo {
switch fields[0] {
case "MemTotal:":
memTotal = val
- case "MemFree:":
- memFree = val
+ case "MemAvailable:":
+ memAvailable = val
case "SwapTotal:":
swapTotal = val
case "SwapFree:":
@@ -804,9 +930,9 @@ func getLinuxSysInfo() SystemInfo {
}
}
info.MemTotal = memTotal
- info.MemFree = memFree
+ info.MemUsed = memTotal - memAvailable
info.SwapTotal = swapTotal
- info.SwapFree = swapFree
+ info.SwapUsed = swapTotal - swapFree
}
// 网络I/O:解析/proc/net/dev
@@ -914,9 +1040,12 @@ func (m *Master) handleInstances(w http.ResponseWriter, r *http.Request) {
Type: instanceType,
URL: m.enhanceURL(reqData.URL, instanceType),
Status: "stopped",
- Restart: false,
+ Restart: true,
+ Tags: []Tag{},
stopped: make(chan struct{}),
}
+
+ instance.Config = m.generateConfigURL(instance)
m.instances.Store(id, instance)
// 启动实例
@@ -978,6 +1107,7 @@ func (m *Master) handlePatchInstance(w http.ResponseWriter, r *http.Request, id
Alias string `json:"alias,omitempty"`
Action string `json:"action,omitempty"`
Restart *bool `json:"restart,omitempty"`
+ Tags []Tag `json:"tags,omitempty"`
}
if err := json.NewDecoder(r.Body).Decode(&reqData); err == nil {
if id == apiKeyID {
@@ -988,6 +1118,44 @@ func (m *Master) handlePatchInstance(w http.ResponseWriter, r *http.Request, id
m.sendSSEEvent("update", instance)
}
} else {
+ // 处理标签更新
+ if reqData.Tags != nil {
+ if err := validateTags(reqData.Tags); err != nil {
+ httpError(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ // 创建现有标签的映射表
+ existingTags := make(map[string]Tag)
+ for _, tag := range instance.Tags {
+ existingTags[tag.Key] = tag
+ }
+
+ for _, tag := range reqData.Tags {
+ if tag.Value == "" {
+ // value为空,删除key
+ delete(existingTags, tag.Key)
+ } else {
+ // value非空,更新或添加key
+ existingTags[tag.Key] = tag
+ }
+ }
+
+ // 将映射表转换回标签数组
+ newTags := make([]Tag, 0, len(existingTags))
+ for _, tag := range existingTags {
+ newTags = append(newTags, tag)
+ }
+
+ instance.Tags = newTags
+ m.instances.Store(id, instance)
+ go m.saveState()
+ m.logger.Info("Tags updated: [%v]", instance.ID)
+
+ // 发送标签变更事件
+ m.sendSSEEvent("update", instance)
+ }
+
// 重置流量统计
if reqData.Action == "reset" {
instance.TCPRX = 0
@@ -1015,6 +1183,10 @@ func (m *Master) handlePatchInstance(w http.ResponseWriter, r *http.Request, id
// 更新实例别名
if reqData.Alias != "" && instance.Alias != reqData.Alias {
+ if len(reqData.Alias) > maxTagKeyLen {
+ httpError(w, fmt.Sprintf("Instance alias exceeds maximum length %d", maxTagKeyLen), http.StatusBadRequest)
+ return
+ }
instance.Alias = reqData.Alias
m.instances.Store(id, instance)
go m.saveState()
@@ -1072,8 +1244,8 @@ func (m *Master) handlePutInstance(w http.ResponseWriter, r *http.Request, id st
return
}
- // 如果实例正在运行,先停止它
- if instance.Status == "running" {
+ // 如果实例需要停止,先停止它
+ if instance.Status != "stopped" {
m.stopInstance(instance)
time.Sleep(baseDuration)
}
@@ -1081,6 +1253,7 @@ func (m *Master) handlePutInstance(w http.ResponseWriter, r *http.Request, id st
// 更新实例URL和类型
instance.URL = enhancedURL
instance.Type = instanceType
+ instance.Config = m.generateConfigURL(instance)
// 更新实例状态
instance.Status = "stopped"
@@ -1103,8 +1276,9 @@ func (m *Master) handlePutInstance(w http.ResponseWriter, r *http.Request, id st
func (m *Master) regenerateAPIKey(instance *Instance) {
instance.URL = generateAPIKey()
m.instances.Store(apiKeyID, instance)
- go m.saveState()
m.logger.Info("API Key regenerated: %v", instance.URL)
+ go m.saveState()
+ go m.shutdownSSEConnections()
}
// processInstanceAction 处理实例操作
@@ -1115,19 +1289,15 @@ func (m *Master) processInstanceAction(instance *Instance, action string) {
go m.startInstance(instance)
}
case "stop":
- if instance.Status == "running" {
+ if instance.Status != "stopped" {
go m.stopInstance(instance)
}
case "restart":
- if instance.Status == "running" {
- go func() {
- m.stopInstance(instance)
- time.Sleep(baseDuration)
- m.startInstance(instance)
- }()
- } else {
- go m.startInstance(instance)
- }
+ go func() {
+ m.stopInstance(instance)
+ time.Sleep(baseDuration)
+ m.startInstance(instance)
+ }()
}
}
@@ -1139,7 +1309,11 @@ func (m *Master) handleDeleteInstance(w http.ResponseWriter, id string, instance
return
}
- if instance.Status == "running" {
+ // 标记实例为已删除
+ instance.deleted = true
+ m.instances.Store(id, instance)
+
+ if instance.Status != "stopped" {
m.stopInstance(instance)
}
m.instances.Delete(id)
@@ -1202,12 +1376,14 @@ func (m *Master) handleSSE(w http.ResponseWriter, r *http.Request) {
// 客户端连接关闭标志
connectionClosed := make(chan struct{})
- // 监听客户端连接是否关闭,但不关闭通道,留给Shutdown处理
+ // 监听客户端连接是否关闭
go func() {
<-ctx.Done()
close(connectionClosed)
- // 只从映射表中移除,但不关闭通道
- m.subscribers.Delete(subscriberID)
+ // 从映射表中移除并关闭通道
+ if ch, exists := m.subscribers.LoadAndDelete(subscriberID); exists {
+ close(ch.(chan *InstanceEvent))
+ }
}()
// 持续发送事件到客户端
@@ -1255,6 +1431,32 @@ func (m *Master) sendSSEEvent(eventType string, instance *Instance, logs ...stri
}
}
+// shutdownSSEConnections 通知并关闭SSE连接
+func (m *Master) shutdownSSEConnections() {
+ var wg sync.WaitGroup
+
+ // 发送shutdown通知并关闭通道
+ m.subscribers.Range(func(key, value any) bool {
+ ch := value.(chan *InstanceEvent)
+ wg.Add(1)
+ go func(subscriberID any, eventChan chan *InstanceEvent) {
+ defer wg.Done()
+ // 发送shutdown通知
+ select {
+ case eventChan <- &InstanceEvent{Type: "shutdown", Time: time.Now()}:
+ default:
+ }
+ // 从映射表中移除并关闭通道
+ if _, exists := m.subscribers.LoadAndDelete(subscriberID); exists {
+ close(eventChan)
+ }
+ }(key, ch)
+ return true
+ })
+
+ wg.Wait()
+}
+
// startEventDispatcher 启动事件分发器
func (m *Master) startEventDispatcher() {
for event := range m.notifyChannel {
@@ -1345,9 +1547,7 @@ func (m *Master) startInstance(instance *Instance) {
// monitorInstance 监控实例状态
func (m *Master) monitorInstance(instance *Instance, cmd *exec.Cmd) {
done := make(chan error, 1)
- go func() {
- done <- cmd.Wait()
- }()
+ go func() { done <- cmd.Wait() }()
for {
select {
@@ -1371,7 +1571,7 @@ func (m *Master) monitorInstance(instance *Instance, cmd *exec.Cmd) {
}
return
case <-time.After(reportInterval):
- if !instance.lastCheckPoint.IsZero() && time.Since(instance.lastCheckPoint) > 5*reportInterval {
+ if !instance.lastCheckPoint.IsZero() && time.Since(instance.lastCheckPoint) > 3*reportInterval {
instance.Status = "error"
m.instances.Store(instance.ID, instance)
m.sendSSEEvent("update", instance)
@@ -1469,6 +1669,86 @@ func (m *Master) enhanceURL(instanceURL string, instanceType string) string {
return parsedURL.String()
}
+// generateConfigURL 生成实例的完整URL
+func (m *Master) generateConfigURL(instance *Instance) string {
+ parsedURL, err := url.Parse(instance.URL)
+ if err != nil {
+ m.logger.Error("generateConfigURL: invalid URL format: %v", err)
+ return instance.URL
+ }
+
+ query := parsedURL.Query()
+
+ // 设置日志级别
+ if m.logLevel != "" && query.Get("log") == "" {
+ query.Set("log", m.logLevel)
+ }
+
+ // 设置TLS配置
+ if instance.Type == "server" && m.tlsCode != "0" {
+ if query.Get("tls") == "" {
+ query.Set("tls", m.tlsCode)
+ }
+
+ // 为TLS code-2设置证书和密钥
+ if m.tlsCode == "2" {
+ if m.crtPath != "" && query.Get("crt") == "" {
+ query.Set("crt", m.crtPath)
+ }
+ if m.keyPath != "" && query.Get("key") == "" {
+ query.Set("key", m.keyPath)
+ }
+ }
+ }
+
+ // 根据实例类型设置默认参数
+ switch instance.Type {
+ case "client":
+ // client参数: min, mode, read, rate, slot, proxy
+ if query.Get("min") == "" {
+ query.Set("min", strconv.Itoa(defaultMinPool))
+ }
+ if query.Get("mode") == "" {
+ query.Set("mode", defaultRunMode)
+ }
+ if query.Get("read") == "" {
+ query.Set("read", defaultReadTimeout.String())
+ }
+ if query.Get("rate") == "" {
+ query.Set("rate", strconv.Itoa(defaultRateLimit))
+ }
+ if query.Get("slot") == "" {
+ query.Set("slot", strconv.Itoa(defaultSlotLimit))
+ }
+ if query.Get("proxy") == "" {
+ query.Set("proxy", defaultProxyProtocol)
+ }
+ case "server":
+ // server参数: max, mode, read, rate, slot, proxy
+ if query.Get("max") == "" {
+ query.Set("max", strconv.Itoa(defaultMaxPool))
+ }
+ if query.Get("mode") == "" {
+ query.Set("mode", defaultRunMode)
+ }
+ if query.Get("read") == "" {
+ query.Set("read", defaultReadTimeout.String())
+ }
+ if query.Get("rate") == "" {
+ query.Set("rate", strconv.Itoa(defaultRateLimit))
+ }
+ if query.Get("slot") == "" {
+ query.Set("slot", strconv.Itoa(defaultSlotLimit))
+ }
+ if query.Get("proxy") == "" {
+ query.Set("proxy", defaultProxyProtocol)
+ }
+ }
+
+ parsedURL.RawQuery = query.Encode()
+ return parsedURL.String()
+}
+
// generateID 生成随机ID
func generateID() string {
bytes := make([]byte, 4)
@@ -1500,7 +1780,7 @@ func writeJSON(w http.ResponseWriter, statusCode int, data any) {
}
// generateOpenAPISpec 生成OpenAPI规范文档
-func generateOpenAPISpec() string {
+func (m *Master) generateOpenAPISpec() string {
return fmt.Sprintf(`{
"openapi": "3.1.1",
"info": {
@@ -1508,7 +1788,7 @@ func generateOpenAPISpec() string {
"description": "API for managing NodePass server and client instances",
"version": "%s"
},
- "servers": [{"url": "/{prefix}/v1", "variables": {"prefix": {"default": "api", "description": "API prefix path"}}}],
+ "servers": [{"url": "%s"}],
"security": [{"ApiKeyAuth": []}],
"paths": {
"/instances": {
@@ -1606,6 +1886,17 @@ func generateOpenAPISpec() string {
"401": {"description": "Unauthorized"},
"405": {"description": "Method not allowed"}
}
+ },
+ "post": {
+ "summary": "Update master alias",
+ "security": [{"ApiKeyAuth": []}],
+ "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateMasterAliasRequest"}}}},
+ "responses": {
+ "200": {"description": "Success", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MasterInfo"}}}},
+ "400": {"description": "Invalid input"},
+ "401": {"description": "Unauthorized"},
+ "405": {"description": "Method not allowed"}
+ }
}
},
"/tcping": {
@@ -1664,7 +1955,9 @@ func generateOpenAPISpec() string {
"type": {"type": "string", "enum": ["client", "server"], "description": "Type of instance"},
"status": {"type": "string", "enum": ["running", "stopped", "error"], "description": "Instance status"},
"url": {"type": "string", "description": "Command string or API Key"},
+ "config": {"type": "string", "description": "Instance configuration URL"},
"restart": {"type": "boolean", "description": "Restart policy"},
+ "tags": {"type": "array", "items": {"$ref": "#/components/schemas/Tag"}, "description": "Tag array"},
"mode": {"type": "integer", "description": "Instance mode"},
"ping": {"type": "integer", "description": "TCPing latency"},
"pool": {"type": "integer", "description": "Pool active count"},
@@ -1686,7 +1979,8 @@ func generateOpenAPISpec() string {
"properties": {
"alias": {"type": "string", "description": "Instance alias"},
"action": {"type": "string", "enum": ["start", "stop", "restart", "reset"], "description": "Action for the instance"},
- "restart": {"type": "boolean", "description": "Instance restart policy"}
+ "restart": {"type": "boolean", "description": "Instance restart policy"},
+ "tags": {"type": "array", "items": {"$ref": "#/components/schemas/Tag"}, "description": "Tag array"}
}
},
"PutInstanceRequest": {
@@ -1697,13 +1991,14 @@ func generateOpenAPISpec() string {
"MasterInfo": {
"type": "object",
"properties": {
+ "alias": {"type": "string", "description": "Master alias"},
"os": {"type": "string", "description": "Operating system"},
"arch": {"type": "string", "description": "System architecture"},
"cpu": {"type": "integer", "description": "CPU usage percentage"},
"mem_total": {"type": "integer", "format": "int64", "description": "Total memory in bytes"},
- "mem_free": {"type": "integer", "format": "int64", "description": "Free memory in bytes"},
+ "mem_used": {"type": "integer", "format": "int64", "description": "Used memory in bytes"},
"swap_total": {"type": "integer", "format": "int64", "description": "Total swap space in bytes"},
- "swap_free": {"type": "integer", "format": "int64", "description": "Free swap space in bytes"},
+ "swap_used": {"type": "integer", "format": "int64", "description": "Used swap space in bytes"},
"netrx": {"type": "integer", "format": "int64", "description": "Network received bytes"},
"nettx": {"type": "integer", "format": "int64", "description": "Network transmitted bytes"},
"diskr": {"type": "integer", "format": "int64", "description": "Disk read bytes"},
@@ -1718,6 +2013,11 @@ func generateOpenAPISpec() string {
"key": {"type": "string", "description": "Private key path"}
}
},
+ "UpdateMasterAliasRequest": {
+ "type": "object",
+ "required": ["alias"],
+ "properties": {"alias": {"type": "string", "description": "Master alias"}}
+ },
"TCPingResult": {
"type": "object",
"properties": {
@@ -1726,8 +2026,16 @@ func generateOpenAPISpec() string {
"latency": {"type": "integer", "format": "int64", "description": "Latency in milliseconds"},
"error": {"type": "string", "nullable": true, "description": "Error message"}
}
+ },
+ "Tag": {
+ "type": "object",
+ "required": ["key", "value"],
+ "properties": {
+ "key": {"type": "string", "description": "Tag key"},
+ "value": {"type": "string", "description": "Tag value"}
+ }
}
}
}
-}`, openAPIVersion)
+}`, openAPIVersion, m.prefix)
}
diff --git a/nodepass/internal/server.go b/nodepass/internal/server.go
index 2abbc267b6..da00f86200 100644
--- a/nodepass/internal/server.go
+++ b/nodepass/internal/server.go
@@ -3,7 +3,6 @@ package internal
import (
"bufio"
- "bytes"
"context"
"crypto/tls"
"fmt"
@@ -13,6 +12,7 @@ import (
"os"
"os/signal"
"strconv"
+ "sync"
"syscall"
"time"
@@ -29,26 +29,40 @@ type Server struct {
}
// NewServer 创建新的服务端实例
-func NewServer(parsedURL *url.URL, tlsCode string, tlsConfig *tls.Config, logger *logs.Logger) *Server {
+func NewServer(parsedURL *url.URL, tlsCode string, tlsConfig *tls.Config, logger *logs.Logger) (*Server, error) {
server := &Server{
Common: Common{
tlsCode: tlsCode,
logger: logger,
signalChan: make(chan string, semaphoreLimit),
+ tcpBufferPool: &sync.Pool{
+ New: func() any {
+ buf := make([]byte, tcpDataBufSize)
+ return &buf
+ },
+ },
+ udpBufferPool: &sync.Pool{
+ New: func() any {
+ buf := make([]byte, udpDataBufSize)
+ return &buf
+ },
+ },
},
tlsConfig: tlsConfig,
}
- server.initConfig(parsedURL)
+ if err := server.initConfig(parsedURL); err != nil {
+ return nil, fmt.Errorf("newServer: initConfig failed: %w", err)
+ }
server.initRateLimiter()
- return server
+ return server, nil
}
// Run 管理服务端生命周期
func (s *Server) Run() {
logInfo := func(prefix string) {
- s.logger.Info("%v: %v@%v/%v?max=%v&mode=%v&read=%v&rate=%v&slot=%v",
+ s.logger.Info("%v: server://%v@%v/%v?max=%v&mode=%v&read=%v&rate=%v&slot=%v&proxy=%v",
prefix, s.tunnelKey, s.tunnelTCPAddr, s.targetTCPAddr,
- s.maxPoolCapacity, s.runMode, s.readTimeout, s.rateLimit/125000, s.slotLimit)
+ s.maxPoolCapacity, s.runMode, s.readTimeout, s.rateLimit/125000, s.slotLimit, s.proxyProtocol)
}
logInfo("Server started")
@@ -168,7 +182,7 @@ func (s *Server) tunnelHandshake() error {
tunnelTCPConn.SetReadDeadline(time.Now().Add(handshakeTimeout))
bufReader := bufio.NewReader(tunnelTCPConn)
- rawTunnelKey, err := bufReader.ReadString('\n')
+ rawTunnelKey, err := bufReader.ReadBytes('\n')
if err != nil {
s.logger.Warn("tunnelHandshake: handshake timeout: %v", tunnelTCPConn.RemoteAddr())
tunnelTCPConn.Close()
@@ -181,7 +195,20 @@ func (s *Server) tunnelHandshake() error {
}
tunnelTCPConn.SetReadDeadline(time.Time{})
- tunnelKey := string(s.xor(bytes.TrimSuffix([]byte(rawTunnelKey), []byte{'\n'})))
+
+ // 解码隧道密钥
+ tunnelKeyData, err := s.decode(rawTunnelKey)
+ if err != nil {
+ s.logger.Warn("tunnelHandshake: decode tunnel key failed: %v", tunnelTCPConn.RemoteAddr())
+ tunnelTCPConn.Close()
+ select {
+ case <-s.ctx.Done():
+ return fmt.Errorf("tunnelHandshake: context error: %w", s.ctx.Err())
+ case <-time.After(serviceCooldown):
+ }
+ continue
+ }
+ tunnelKey := string(tunnelKeyData)
if tunnelKey != s.tunnelKey {
s.logger.Warn("tunnelHandshake: access denied: %v", tunnelTCPConn.RemoteAddr())
@@ -212,7 +239,7 @@ func (s *Server) tunnelHandshake() error {
Fragment: s.tlsCode,
}
- _, err := s.tunnelTCPConn.Write(append(s.xor([]byte(tunnelURL.String())), '\n'))
+ _, err := s.tunnelTCPConn.Write(s.encode([]byte(tunnelURL.String())))
if err != nil {
return fmt.Errorf("tunnelHandshake: write tunnel config failed: %w", err)
}
diff --git a/openwrt-packages/luci-app-unblockneteasemusic/Makefile b/openwrt-packages/luci-app-unblockneteasemusic/Makefile
index e9d4133163..cbb024318d 100644
--- a/openwrt-packages/luci-app-unblockneteasemusic/Makefile
+++ b/openwrt-packages/luci-app-unblockneteasemusic/Makefile
@@ -10,8 +10,8 @@ LUCI_DEPENDS:=+dnsmasq-full +ipset +node \
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-unblockneteasemusic
-PKG_VERSION:=2.14
-PKG_RELEASE:=3
+PKG_VERSION:=2.15
+PKG_RELEASE:=1
PKG_MAINTAINER:=Tianling Shen
diff --git a/openwrt-packages/luci-app-unblockneteasemusic/luasrc/model/cbi/unblockneteasemusic/main.lua b/openwrt-packages/luci-app-unblockneteasemusic/luasrc/model/cbi/unblockneteasemusic/main.lua
index 66fb932291..1ee4d97dce 100644
--- a/openwrt-packages/luci-app-unblockneteasemusic/luasrc/model/cbi/unblockneteasemusic/main.lua
+++ b/openwrt-packages/luci-app-unblockneteasemusic/luasrc/model/cbi/unblockneteasemusic/main.lua
@@ -16,6 +16,7 @@ o = s:option(Value, "music_source", translate("音源接口"))
o:value("default", translate("默认"))
o:value("bilibili", translate("Bilibili音乐"))
o:value("bilivideo", translate("Bilibili音乐(bilivideo)"))
+o:value("bodian", translate("波点音乐"))
o:value("joox", translate("JOOX音乐"))
o:value("kugou", translate("酷狗音乐"))
o:value("kuwo", translate("酷我音乐"))
diff --git a/openwrt-packages/luci-app-unblockneteasemusic/root/etc/init.d/unblockneteasemusic b/openwrt-packages/luci-app-unblockneteasemusic/root/etc/init.d/unblockneteasemusic
index 789fc1fe6d..8e69e2e62f 100755
--- a/openwrt-packages/luci-app-unblockneteasemusic/root/etc/init.d/unblockneteasemusic
+++ b/openwrt-packages/luci-app-unblockneteasemusic/root/etc/init.d/unblockneteasemusic
@@ -166,9 +166,8 @@ start_service() {
ipset create "neteasemusic" hash:ip timeout 7200
config_foreach append_filter_client "acl_rule"
- local netease_music_ips="$(wget -qO- "http://httpdns.n.netease.com/httpdns/v2/d?domain=music.163.com,interface.music.163.com,interface3.music.163.com,apm.music.163.com,apm3.music.163.com,clientlog.music.163.com,clientlog3.music.163.com" |jsonfilter -e '@.data.*.ip.*')"
- local netease_music_ips2="$(wget -qO- "https://music.httpdns.c.163.com/d" --post-data="music.163.com,interface.music.163.com,interface3.music.163.com,apm.music.163.com,apm3.music.163.com,clientlog.music.163.com,clientlog3.music.163.com" |jsonfilter -e '@.dns.*["ips"].*')"
- echo -e "${netease_music_ips}\n${netease_music_ips2}" | sort -u | awk '{print "ipset add neteasemusic "$1}' | sh
+ local netease_music_ips="$(wget -T10 -qO- "http://httpdns.n.netease.com/httpdns/v2/d?domain=music.163.com,interface.music.163.com,interface3.music.163.com,apm.music.163.com,apm3.music.163.com,clientlog.music.163.com,clientlog3.music.163.com" |jsonfilter -e '@.data.*.ip.*')"
+ echo -e "${netease_music_ips}" | sort -u | awk '{print "ipset add neteasemusic "$1}' | sh
$IPT_N -N "netease_cloud_music"
for local_addr in "0.0.0.0/8" "10.0.0.0/8" "127.0.0.0/8" "169.254.0.0/16" "172.16.0.0/12" "192.168.0.0/16" "224.0.0.0/4" "240.0.0.0/4"; do
diff --git a/openwrt-packages/luci-app-unblockneteasemusic/root/usr/share/unblockneteasemusic/debugging.sh b/openwrt-packages/luci-app-unblockneteasemusic/root/usr/share/unblockneteasemusic/debugging.sh
index a4b8098ad3..5a2af4b5e1 100755
--- a/openwrt-packages/luci-app-unblockneteasemusic/root/usr/share/unblockneteasemusic/debugging.sh
+++ b/openwrt-packages/luci-app-unblockneteasemusic/root/usr/share/unblockneteasemusic/debugging.sh
@@ -18,7 +18,7 @@ echo -e "uclient-fetch info:"
opkg info uclient-fetch
opkg info libustream-*
opkg info wget-ssl
-wget -O- 'https://api.github.com/repos/UnblockNeteaseMusic/server/commits?sha=enhanced&path=precompiled' | jsonfilter -e '@[0].sha' || echo -e "Failed to connect to GitHub with uclient-fetch."
+wget -T10 -O- 'https://api.github.com/repos/UnblockNeteaseMusic/server/commits?sha=enhanced&path=precompiled' | jsonfilter -e '@[0].sha' || echo -e "Failed to connect to GitHub with uclient-fetch."
echo -e "\n"
echo -e "Node.js info:"
diff --git a/openwrt-packages/luci-app-unblockneteasemusic/root/usr/share/unblockneteasemusic/update.sh b/openwrt-packages/luci-app-unblockneteasemusic/root/usr/share/unblockneteasemusic/update.sh
index abebf0b18f..06f41c5211 100755
--- a/openwrt-packages/luci-app-unblockneteasemusic/root/usr/share/unblockneteasemusic/update.sh
+++ b/openwrt-packages/luci-app-unblockneteasemusic/root/usr/share/unblockneteasemusic/update.sh
@@ -10,22 +10,19 @@ mkdir -p "$RUN_DIR"
LOCK="$RUN_DIR/update_core.lock"
LOG="$RUN_DIR/run.log"
-check_core_if_already_running() {
- if [ -e "$LOCK" ]; then
- echo -e "\nA task is already running." >> "$LOG"
- exit 2
- else
- touch "$LOCK"
- fi
-}
-
clean_log(){
echo "" > "$LOG"
}
check_core_latest_version() {
- core_latest_ver="$(wget -qO- 'https://api.github.com/repos/UnblockNeteaseMusic/server/commits?sha=enhanced&path=precompiled' | jsonfilter -e '@[0].sha')"
- [ -n "$core_latest_ver" ] || { echo -e "\nFailed to check latest core version, please try again later." >> "$LOG"; rm -f "$LOCK"; exit 1; }
+ exec 200>"$LOCK"
+ if ! flock -n 200 &> /dev/null; then
+ echo -e "\nA task is already running." >> "$LOG"
+ exit 2
+ fi
+
+ core_latest_ver="$(wget -T10 -qO- 'https://api.github.com/repos/UnblockNeteaseMusic/server/commits?sha=enhanced&path=precompiled' | jsonfilter -e '@[0].sha')"
+ [ -n "$core_latest_ver" ] || { echo -e "\nFailed to check latest core version, please try again later." >> "$LOG"; exit 1; }
if [ ! -e "$UNM_DIR/core_local_ver" ]; then
clean_log
echo -e "Local version: NOT FOUND, latest version: $core_latest_ver." >> "$LOG"
@@ -38,7 +35,6 @@ check_core_latest_version() {
else
echo -e "\nLocal version: $(cat $UNM_DIR/core_local_ver 2>"/dev/null"), latest version: $core_latest_ver." >> "$LOG"
echo -e "You're already using the latest version." >> "$LOG"
- rm -f "$LOCK"
exit 3
fi
fi
@@ -50,22 +46,20 @@ update_core() {
mkdir -p "$UNM_DIR/core"
rm -rf "$UNM_DIR/core"/*
- for file in $(wget -qO- "https://api.github.com/repos/UnblockNeteaseMusic/server/contents/precompiled" | jsonfilter -e '@[*].path')
+ for file in $(wget -T10 -qO- "https://api.github.com/repos/UnblockNeteaseMusic/server/contents/precompiled" | jsonfilter -e '@[*].path')
do
- wget "https://fastly.jsdelivr.net/gh/UnblockNeteaseMusic/server@$core_latest_ver/$file" -qO "$UNM_DIR/core/${file##*/}"
+ wget -T10 "https://fastly.jsdelivr.net/gh/UnblockNeteaseMusic/server@$core_latest_ver/$file" -qO "$UNM_DIR/core/${file##*/}"
[ -s "$UNM_DIR/core/${file##*/}" ] || {
echo -e "Failed to download ${file##*/}." >> "$LOG"
- rm -f "$LOCK"
exit 1
}
done
for cert in "ca.crt" "server.crt" "server.key"
do
- wget "https://fastly.jsdelivr.net/gh/UnblockNeteaseMusic/server@$core_latest_ver/$cert" -qO "$UNM_DIR/core/$cert"
+ wget -T10 "https://fastly.jsdelivr.net/gh/UnblockNeteaseMusic/server@$core_latest_ver/$cert" -qO "$UNM_DIR/core/$cert"
[ -s "$UNM_DIR/core/${cert}" ] || {
echo -e "Failed to download ${cert}." >> "$LOG"
- rm -f "$LOCK"
exit 1
}
done
@@ -76,17 +70,14 @@ update_core() {
echo -e "Succeeded in updating core." > "$LOG"
echo -e "Current core version: $core_latest_ver.\n" >> "$LOG"
- rm -f "$LOCK"
}
case "$1" in
"update_core")
- check_core_if_already_running
check_core_latest_version
;;
"update_core_non_restart")
non_restart=1
- check_core_if_already_running
check_core_latest_version
;;
"update_core_from_luci")
diff --git a/small/luci-app-bypass/Makefile b/small/luci-app-bypass/Makefile
index 98ba96401d..45666fc5a8 100644
--- a/small/luci-app-bypass/Makefile
+++ b/small/luci-app-bypass/Makefile
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-bypass
PKG_VERSION:=1.2
-PKG_RELEASE:=21
+PKG_RELEASE:=22
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \
diff --git a/small/luci-app-bypass/root/usr/share/ucitrack/luci-app-bypass.json b/small/luci-app-bypass/root/usr/share/ucitrack/luci-app-bypass.json
new file mode 100644
index 0000000000..e62d9b25ed
--- /dev/null
+++ b/small/luci-app-bypass/root/usr/share/ucitrack/luci-app-bypass.json
@@ -0,0 +1,4 @@
+{
+ "config": "bypass",
+ "init": "bypass"
+}
diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js b/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js
index 191d9613a5..bcfa13b060 100644
--- a/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js
+++ b/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js
@@ -317,6 +317,37 @@ const tls_client_fingerprints = [
['random']
];
+const vless_encryption = {
+ methods: [
+ ['mlkem768x25519plus', _('mlkem768x25519plus')]
+ ],
+ xormodes: [
+ ['native', 'native', _('Native appearance')],
+ ['xorpub', 'xorpub', _('Eliminate encryption header characteristics')],
+ ['random', 'random', _('Randomized traffic characteristics')]
+ ],
+ tickets: [
+ ['600s', '600s', _('Send random ticket of 300s-600s duration for client 0-RTT reuse.')],
+ ['300-600s', '300-600s', _('Send random ticket of 300s-600s duration for client 0-RTT reuse.')],
+ ['0s', '0s', _('1-RTT only.')]
+ ],
+ rtts: [
+ ['0rtt', _('0-RTT reuse.') +' '+ _('Requires server support.')],
+ ['1rtt', _('1-RTT only.')]
+ ],
+ paddings: [
+ ['100-111-1111', '100-111-1111: ' + _('After the 1-RTT client/server hello, padding randomly 111-1111 bytes with 100% probability.')],
+ ['75-0-111', '75-0-111: ' + _('Wait a random 0-111 milliseconds with 75% probability.')],
+ ['50-0-3333', '50-0-3333: ' + _('Send padding randomly 0-3333 bytes with 50% probability.')]
+ ],
+ keypairs: {
+ types: [
+ ['vless-x25519', _('vless-x25519')],
+ ['vless-mlkem768', _('vless-mlkem768')]
+ ]
+ }
+};
+
const vless_flow = [
['', _('None')],
['xtls-rprx-vision']
@@ -722,6 +753,34 @@ function decodeBase64Str(str) {
).join(''));
}
+function encodeBase64Str(str) {
+ if (!str)
+ return null;
+
+ let buf = encodeURIComponent(str).split('%').slice(1).map(h => parseInt(h, 16));
+ return btoa(String.fromCharCode(...buf));
+}
+
+function decodeBase64Bin(str) {
+ if (!str)
+ return null;
+
+ /* Thanks to luci-app-ssr-plus */
+ str = str.replace(/-/g, '+').replace(/_/g, '/');
+ let padding = (4 - (str.length % 4)) % 4;
+ if (padding)
+ str = str + Array(padding + 1).join('=');
+
+ return Array.prototype.map.call(atob(str), c => c.charCodeAt(0)); // OR Uint8Array.fromBase64(str);
+}
+
+function encodeBase64Bin(buf) {
+ if (isEmpty(buf))
+ return null;
+
+ return btoa(String.fromCharCode(...buf)); // OR new Uint8Array(buf).toBase64();
+}
+
function generateRand(type, length) {
let byteArr;
if (['base64', 'hex'].includes(type))
@@ -1008,372 +1067,6 @@ function renderResDownload(section_id) {
return El;
}
-function renderVlessEncryption(s, uciconfig) {
- // ref: https://github.com/XTLS/Xray-core/pull/5067
- // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L64
- // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/server.go#L42
-
-/* {
- "method": "mlkem768x25519plus",
- "xormode": "native",
- "ticket": "600s",
- "paddings": [ // Optional
- "100-111-1111",
- "75-0-111",
- "50-0-3333",
- ...
- ],
- "keypairs": [
- {
- "type": "vless-x25519",
- "private_key": "cP5Oy9MOpTaBKKE17Pfd56mbb1CIfp5EMpyBYqr2EG8",
- "password": "khEcQMT8j41xWmGYKpZtQ4vd8_9VWyFVmmCDIhRJ-Uk"
- },
- {
- "type": "vless-mlkem768",
- "seed": "UHPx3nf-FVxF95byAw0YG025aQNw9HxKej-MiG5AhTcdW_WFpHlTVYQU5NHmXP6tmljSnB2iPmSQ29fisGxEog",
- "client": "h4sdZgCc5-ZefvQ8mZmReOWQdxYb0mwngMdl7pKhYEZZpGWHUPKAmxug87Bgj3GqSHs195QeVpxfrMLNB5Jm0Ge71Fc-A3aLpaS3C3pARbGQoquUDUEVDNwEWjQTvFpGTUV3Nddw_LlRmWN6Wqhguti9cpS6GhEmkBvBayFeHgZosuaQ1FMoAqIeQzSSSoguCZtGLUmdQjEs3zc5rwG1rNanbhtyI3QnooYvr3A0vggIkbmddjtjwYaVQdMAj9Moavc12EAUajOV91QA73RWVuhelbe7pLumsHiW-emIdBgVhEgDDYdGaLq1E8QjB0WbIfufnJp-CJa3Ieu9gmDASTlQBeEREeA9gfoZcTpYD8elhJIJxaPJKXchvUVkFhZarcivlKoqVuaFPzsJM7KQCBC8zfS0t_oiBka-uzg3_Hl153nMTDaCAbZULPZGE-p2EazI2eFBCDktdHtDffJNo7i7ZYSkWkqN9ysr2QZRvYG_PYCzcYSo34Gf5WNvHKuz0Ye3kFkckfuirCmzr3knw2azrSOmpTOX_RSlMlse7HgFYwxHPMJnzPS19ymiwKZPgrAMvCmAUZmsxZGDoKeusNEDGSSFhLcTQys20qGBGYasIgKYGjAKGjK7SCxSOCGBQSU496XBkXQEeOB7k9Sh8jdB0pQGAZw9Ntwvrts2DjIUcsQBv-XEGfnHQXoBmDgzwzYEWxeHd0oNbPIlz7CqvNseoKu6uPZl85xynum6aWd6BDDAtwobbqYkuMUfOUhXf_cH13kWSnuJ6QrOxah94JzAnda3tWRDQ3RajOOjk-OXhbOqi8QMJRFdA_C-xMwQalM_rTSTKOqyCcaNSTkVmMlmyOt90tptk7jKUizDmGhGbsSU8WMY5mhdZ3eUd5O6gQitiMHI1EqnlaRNsXnKFoJ5yHV82Wp1dhFONCG_dlpqunVJD5bFgpxtdFDD-KmXQTymAalFjxeVl_xdc5xd4XYCYmk5dhEiQBE0J_S3Z6x0tmFORpWG9lESK_OBRSul9oKZh9Vet-UZ8FSOVtNFwbeokRwWpFuFL1dL3UpJeININ2cgUfDNWQlwItkokiFf_Kdy12y2O_hqJtoTpNttNxTOiclDzKM1KHNOjYJgTgydcid3mmJl3eA6ezyrDAw1RLCHBucIvYRfwbkmpYMvnfAaA2DIiaTNaSxX8BUl92V49UVKWlQSp8ijfmmTRHrBMmxKjvBIgHqC6dSMhVUEOMzCKXAO3giCS3eZzdrNQGhhqTxpYYnFf6uLoKOIiaGY-ByI1YoIVXxX8aCTOOpesFvHjwOKBEoj4Hoxd3iFMUJQazR7P2drnfmS11kgipM7pSUgB7POKwxEF0NQCedM41wVIuoathAqD6N6qalwQ6iOKlZOBUwwMVAMRDJ3aomG37ZeLYhv6fB0-pUUJSN1q4knjtkLFIJSUrih9FZ0XnOll_aeEgOICqQkb4aOMrovjcJEWvgdjUqGPdyIGgkurfqBRHih3dukUcYxt6Y__4KLQ7acqMx0FOFv0ZxFRTCIRGj_GAlFWUi6fpuPKebXUnEn1PRE0iNXwUV_4jESWb0"
- },
- ...
- ]
- } */
-
- const vless_encryption = {
- methods: [
- ['mlkem768x25519plus', _('mlkem768x25519plus')]
- ],
- xormodes: [
- ['native', 'native', _('Native appearance')],
- ['xorpub', 'xorpub', _('Eliminate encryption header characteristics')],
- ['random', 'random', _('Randomized traffic characteristics')]
- ],
- tickets: [
- ['600s', '600s', _('Send random ticket of 300s-600s duration for client 0-RTT reuse.')],
- ['300-600s', '300-600s', _('Send random ticket of 300s-600s duration for client 0-RTT reuse.')],
- ['0s', '0s', _('1-RTT only.')]
- ],
- rtts: [
- ['0rtt', _('0-RTT reuse.') +' '+ _('Requires server support.')],
- ['1rtt', _('1-RTT only.')]
- ],
- paddings: [
- ['100-111-1111', '100-111-1111: ' + _('After the 1-RTT client/server hello, padding randomly 111-1111 bytes with 100% probability.')],
- ['75-0-111', '75-0-111: ' + _('Wait a random 0-111 milliseconds with 75% probability.')],
- ['50-0-3333', '50-0-3333: ' + _('Send padding randomly 0-3333 bytes with 50% probability.')]
- ],
- keypairs: {
- types: [
- ['vless-x25519', _('vless-x25519')],
- ['vless-mlkem768', _('vless-mlkem768')]
- ]
- }
- };
-
- const CBIVlessEncryptionValue = form.Value.extend({
- __name__: 'CBI.VlessEncryptionValue',
-
- renderWidget: function(section_id, option_index, cfgvalue) {
- let node = form.Value.prototype.renderWidget.apply(this, arguments);
-
- node.classList.add('control-group');
- node.firstChild.style.width = '30em';
-
- (node.querySelector('.control-group') || node).appendChild(E('button', {
- class: 'cbi-button cbi-button-add',
- click: ui.createHandlerFn(this, async (section_id) => {
- try {
- await navigator.clipboard.writeText(this.formvalue(section_id));
- console.log('Content copied to clipboard!');
- } catch (e) {
- console.error('Failed to copy: ', e);
- }
- /* Deprecated
- let inputEl = document.getElementById(this.cbid(section_id)).querySelector('input');
- inputEl.select();
- document.execCommand("copy");
- inputEl.blur();
- */
- return alert(_('Content copied to clipboard!'));
- }, section_id)
- }, [ _('Copy') ]));
-
- return node;
- },
-
- write: function() {}
- });
-
- class VlessEncryption {
- // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L64
- // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L12
- constructor(payload) {
- this.input = payload || '';
- try {
- let content = JSON.parse(this.input.trim());
- Object.keys(content).forEach(key => this[key] = content[key]);
- } catch {}
-
- this.method ||= vless_encryption.methods[0][0];
- this.xormode ||= vless_encryption.xormodes[0][0];
- this.ticket ||= vless_encryption.tickets[0][0];
- this.rtt ||= vless_encryption.rtts[0][0];
- this.paddings ||= [];
- this.keypairs ||= [];
- }
-
- setKey(key, value) {
- this[key] = value;
-
- return this
- }
-
- _toMihomo(payload, side) {
- if (!['server', 'client'].includes(side))
- throw new Error('Unknown side: ' + side); // `Unknown side: '${side}'`
-
- let required = [
- payload.method,
- payload.xormode,
- side === 'server' ? payload.ticket : side === 'client' ? payload.rtt : null
- ].join('.');
-
- return required +
- (isEmpty(payload.paddings) ? '' : '.' + payload.paddings.join('.')) + // Optional
- (isEmpty(payload.keypairs) ? '' : '.' + payload.keypairs.map(e => e[side]).join('.')); // Required
- }
-
- toString(format, side) {
- format ||= 'json';
-
- let payload = removeBlankAttrs({
- method: this.method,
- xormode: this.xormode,
- ticket: this.ticket,
- rtt: this.rtt,
- paddings: this.paddings || [],
- keypairs: this.keypairs || []
- });
-
- if (format === 'json')
- return JSON.stringify(payload);
- else if (format === 'mihomo')
- return this._toMihomo(payload, side);
- else
- throw new Error(`Unknown format: '${format}'`);
- }
- }
-
- let initRequired = function(o, key, uciconfig) {
- o.load = function(section_id) {
- return new VlessEncryption(uci.get(uciconfig, section_id, 'vless_encryption_hmpayload'))[key];
- }
- o.onchange = function(ev, section_id, value) {
- let UIEl = this.section.getUIElement(section_id, 'vless_encryption_hmpayload');
- let newentry = new VlessEncryption(UIEl.getValue()).setKey(key, value);
-
- UIEl.setValue(newentry.toString());
-
- [
- ['server', '_vless_encryption_decryption'],
- ['client', '_vless_encryption_encryption']
- ].forEach(([side, option]) => {
- UIEl = this.section.getUIElement(section_id, option);
- UIEl.setValue(newentry.toString('mihomo', side));
- });
- }
- o.write = function() {};
- o.rmempty = false;
- o.modalonly = true;
- }
-
- let o;
-
- o = s.taboption('field_vless_encryption', form.Value, 'vless_encryption_hmpayload', _('Payload'));
- o.readonly = true;
- o.depends('vless_decryption', '1');
- //o.depends('vless_encryption', '1');
- o.modalonly = true;
-
- o = s.taboption('field_vless_encryption', CBIVlessEncryptionValue, '_vless_encryption_decryption', _('decryption'));
- o.readonly = true;
- o.depends('vless_decryption', '1');
- o.modalonly = true;
-
- o = s.taboption('field_vless_encryption', CBIVlessEncryptionValue, '_vless_encryption_encryption', _('encryption'));
- o.readonly = true;
- o.depends('vless_decryption', '1');
- //o.depends('vless_encryption', '1');
- o.modalonly = true;
-
- o = s.taboption('field_vless_encryption', form.ListValue, 'vless_encryption_method', _('Encryption method'));
- o.default = vless_encryption.methods[0][0];
- vless_encryption.methods.forEach((res) => {
- o.value.apply(o, res);
- })
- o.depends('vless_decryption', '1');
- //o.depends('vless_encryption', '1');
- initRequired(o, 'method', uciconfig);
-
- o = s.taboption('field_vless_encryption', form.RichListValue, 'vless_encryption_xormode', _('XOR mode'));
- o.default = vless_encryption.xormodes[0][0];
- vless_encryption.xormodes.forEach((res) => {
- o.value.apply(o, res);
- })
- o.depends('vless_decryption', '1');
- //o.depends('vless_encryption', '1');
- initRequired(o, 'xormode', uciconfig);
-
- o = s.taboption('field_vless_encryption', CBIRichValue, 'vless_encryption_ticket', _('Server') +' '+ _('RTT'));
- o.default = vless_encryption.tickets[0][0];
- vless_encryption.tickets.forEach((res) => {
- o.value.apply(o, res);
- })
- o.validate = function(section_id, value) {
- if (!value)
- return true;
-
- if (!value.match(/^(\d+-)?\d+s$/))
- return _('Expecting: %s').format('^(\\d+-)?\\d+s$');
-
- return true;
- }
- o.depends('vless_decryption', '1');
- initRequired(o, 'ticket', uciconfig);
-
- o = s.taboption('field_vless_encryption', form.ListValue, 'vless_encryption_rtt', _('Client') +' '+ _('RTT'));
- o.default = vless_encryption.rtts[0][0];
- vless_encryption.rtts.forEach((res) => {
- o.value.apply(o, res);
- })
- o.depends('vless_decryption', '1');
- //o.depends('vless_encryption', '1');
- initRequired(o, 'rtt', uciconfig);
-
- o = s.taboption('field_vless_encryption', !pr7558_merged ? CBIDynamicList : form.DynamicList, 'vless_encryption_paddings', _('Paddings'), // @pr7558_merged
- _('The server and client can set different padding parameters.') + '' +
- _('In the order of one Padding-Length and one Padding-Interval, infinite concatenation.') + '' +
- _('The first padding must have a probability of 100% and at least 35 bytes.'));
- vless_encryption.paddings.forEach((res) => {
- o.value.apply(o, res);
- })
- o.validate = function(section_id, value) {
- if (!value)
- return true;
-
- if (!value.match(/^\d+(-\d+){2}$/))
- return _('Expecting: %s').format('^\\d+(-\\d+){2}$');
-
- return true;
- }
- o.allowduplicates = true;
- o.depends('vless_decryption', '1');
- //o.depends('vless_encryption', '1');
- initRequired(o, 'paddings', uciconfig);
- o.rmempty = true; // Forced
-
- o = s.taboption('field_vless_encryption', CBIGenText, 'vless_encryption_keypairs', _('Keypairs'));
- o.placeholder = '[\n {\n "type": "vless-x25519",\n "server": "cP5Oy9MOpTaBKKE17Pfd56mbb1CIfp5EMpyBYqr2EG8",\n "client": "khEcQMT8j41xWmGYKpZtQ4vd8_9VWyFVmmCDIhRJ-Uk"\n },\n {\n "type": "vless-mlkem768",\n "server": "UHPx3nf-FVxF95byAw0YG025aQNw9HxKej-MiG5AhTcdW_WFpHlTVYQU5NHmXP6tmljSnB2iPmSQ29fisGxEog",\n "client": "h4sdZgCc5-ZefvQ8mZmReOWQdxYb0mwngMdl7pKhYEZZpGWHUPKAmxug87Bgj3GqSHs195QeVpxfrMLNB5J..."\n },\n ...\n]';
- o.rows = 10;
- o.hm_options = {
- type: vless_encryption.keypairs.types[0][0],
- params: '',
- callback: function(result) {
- const section_id = this.section.section;
- const key_type = this.hm_options.type;
-
- let keypair = {"type": key_type, "server": "", "client": ""};
- switch (key_type) {
- case 'vless-x25519':
- keypair.server = result.private_key;
- keypair.client = result.password;
- break;
- case 'vless-mlkem768':
- keypair.server = result.seed;
- keypair.client = result.client;
- break;
- default:
- break;
- };
-
- let value = [];
- try {
- value = JSON.parse(this.formvalue(section_id).trim());
- } catch {}
- if (!Array.isArray(value))
- value = [];
-
- value.push(keypair);
-
- return [
- [this.option, JSON.stringify(value, null, 2)]
- ]
- }
- }
- o.renderWidget = function(section_id, option_index, cfgvalue) {
- let node = CBITextValue.prototype.renderWidget.apply(this, arguments);
- const cbid = this.cbid(section_id) + '._keytype_select';
- const selected = this.hm_options.type;
-
- let selectEl = E('select', {
- id: cbid,
- class: 'cbi-input-select',
- style: 'width: 10em',
- });
-
- vless_encryption.keypairs.types.forEach(([k, v]) => {
- selectEl.appendChild(E('option', {
- 'value': k,
- 'selected': (k === selected) ? '' : null
- }, [ v ]));
- });
-
- node.appendChild(E('div', { 'class': 'control-group' }, [
- selectEl,
- E('button', {
- class: 'cbi-button cbi-button-add',
- click: ui.createHandlerFn(this, () => {
- this.hm_options.type = document.getElementById(cbid).value;
-
- return handleGenKey.call(this, this.hm_options);
- })
- }, [ _('Generate') ])
- ]));
-
- return node;
- }
- o.depends('vless_decryption', '1');
- //o.depends('vless_encryption', '1');
- initRequired(o, 'keypairs', uciconfig);
- o.load = function(section_id) {
- return JSON.stringify(new VlessEncryption(uci.get(uciconfig, section_id, 'vless_encryption_hmpayload')).keypairs, null, 2);
- }
- o.onchange = null; // Forced
- o.validate = function(section_id, value) {
- let result = validateJson.apply(this, arguments);
-
- if (result === true) {
- let arr = JSON.parse(value.trim());
- if (Array.isArray(arr) && arr.length >= 1) {
- let UIEl = this.section.getUIElement(section_id, 'vless_encryption_hmpayload');
- let newentry = new VlessEncryption(UIEl.getValue()).setKey('keypairs', JSON.parse(value.trim()));
-
- UIEl.setValue(newentry.toString());
-
- [
- ['server', '_vless_encryption_decryption'],
- ['client', '_vless_encryption_encryption']
- ].forEach(([side, option]) => {
- UIEl = this.section.getUIElement(section_id, option);
- UIEl.setValue(newentry.toString('mihomo', side));
- });
- } else
- return _('Expecting: %s').format(_('least one keypair required'));
- return true;
- } else
- return result;
- }
-}
-
function handleGenKey(option) {
const section_id = this.section.section;
const type = this.section.getOption('type')?.formvalue(section_id);
@@ -1839,6 +1532,7 @@ return baseclass.extend({
trojan_cipher_methods,
tls_client_auth_types,
tls_client_fingerprints,
+ vless_encryption,
vless_flow,
/* Prototype */
@@ -1857,6 +1551,9 @@ return baseclass.extend({
bool2str,
calcStringMD5,
decodeBase64Str,
+ encodeBase64Str,
+ decodeBase64Bin,
+ encodeBase64Bin,
generateRand,
getValue,
json2yaml,
@@ -1877,7 +1574,6 @@ return baseclass.extend({
updateStatus,
getDashURL,
renderResDownload,
- renderVlessEncryption,
handleGenKey,
handleReload,
handleRemoveIdles,
diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js
index aea15a00b9..223141c1c6 100644
--- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js
+++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js
@@ -58,6 +58,54 @@ function parseProviderYaml(field, name, cfg) {
return config;
}
+class VlessEncryptionClient {
+ // origin:
+ // https://github.com/XTLS/Xray-core/pull/5067
+ // client:
+ // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L12
+ // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/client.go#L45
+
+ constructor(payload) {
+ this.input = payload || '';
+ let content = String.prototype.split.call(this.input, '.');
+
+ if (content.length >= 4) {
+ this.method = content[0];
+ this.xormode = content[1];
+ this.rtt = content[2];
+ this.paddings = [];
+ this.keypairs = [];
+
+ // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L39
+ content.slice(3).forEach((e) => {
+ if (e.length < 20)
+ this.paddings.push(e);
+ else
+ this.keypairs.push(e);
+ });
+ } else
+ console.error('Invalid VLESS encryption value: ' + payload);
+ }
+
+ setKey(key, value) {
+ this[key] = value;
+
+ return this
+ }
+
+ toString() {
+ let required = [
+ this.method,
+ this.xormode,
+ this.rtt
+ ].join('.');
+
+ return required +
+ (hm.isEmpty(this.paddings) ? '' : '.' + this.paddings.join('.')) + // Optional
+ (hm.isEmpty(this.keypairs) ? '' : '.' + this.keypairs.join('.')); // Required
+ }
+}
+
return view.extend({
load() {
return Promise.all([
@@ -558,10 +606,72 @@ return view.extend({
so.modalonly = true;
/* Vless Encryption fields */
- so = ss.taboption('field_general', form.Value, 'vless_encryption', _('encryption'));
+ so = ss.taboption('field_general', form.Flag, 'vless_encryption', _('encryption'));
+ so.default = so.disabled;
so.depends('type', 'vless');
so.modalonly = true;
+ const initVlessEncryptionClientOption = function(o, key) {
+ o.load = function(section_id) {
+ const value = uci.get(data[0], section_id, 'vless_encryption_encryption');
+
+ if (!value)
+ return null;
+
+ return new VlessEncryptionClient(value)[key];
+ }
+ o.onchange = function(ev, section_id, value) {
+ let UIEl = this.section.getUIElement(section_id, 'vless_encryption_encryption');
+ let newpayload = new VlessEncryptionClient(UIEl.getValue()).setKey(key, value);
+
+ UIEl.setValue(newpayload.toString());
+ }
+ o.write = function() {};
+ }
+
+ so = ss.taboption('field_vless_encryption', form.Value, 'vless_encryption_encryption', _('encryption'));
+ so.renderWidget = function(section_id, option_index, cfgvalue) {
+ let node = form.Value.prototype.renderWidget.apply(this, arguments);
+
+ node.firstChild.style.width = '30em';
+
+ return node;
+ },
+ so.rmempty = false;
+ so.depends('vless_encryption', '1');
+ so.modalonly = true;
+
+ so = ss.taboption('field_vless_encryption', form.ListValue, 'vless_encryption_rtt', _('Client') +' '+ _('RTT'));
+ so.default = hm.vless_encryption.rtts[0][0];
+ hm.vless_encryption.rtts.forEach((res) => {
+ so.value.apply(so, res);
+ })
+ initVlessEncryptionClientOption(so, 'rtt');
+ so.rmempty = false;
+ so.depends('vless_encryption', '1');
+ so.modalonly = true;
+
+ so = ss.taboption('field_vless_encryption', !hm.pr7558_merged ? hm.DynamicList : form.DynamicList, 'vless_encryption_paddings', _('Paddings'), // @pr7558_merged
+ _('The server and client can set different padding parameters.') + '' +
+ _('In the order of one Padding-Length and one Padding-Interval, infinite concatenation.') + '' +
+ _('The first padding must have a probability of 100% and at least 35 bytes.'));
+ hm.vless_encryption.paddings.forEach((res) => {
+ so.value.apply(so, res);
+ })
+ initVlessEncryptionClientOption(so, 'paddings');
+ so.validate = function(section_id, value) {
+ if (!value)
+ return true;
+
+ if (!value.match(/^\d+(-\d+){2}$/))
+ return _('Expecting: %s').format('^\\d+(-\\d+){2}$');
+
+ return true;
+ }
+ so.allowduplicates = true;
+ so.depends('vless_encryption', '1');
+ so.modalonly = true;
+
/* TLS fields */
so = ss.taboption('field_general', form.Flag, 'tls', _('TLS'));
so.default = so.disabled;
diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/server.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/server.js
index 318d2b7719..98aefaf899 100644
--- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/server.js
+++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/server.js
@@ -7,6 +7,135 @@
'require fchomo as hm';
+const CBIDummyCopyValue = form.Value.extend({
+ __name__: 'CBI.DummyCopyValue',
+
+ readonly: true,
+
+ renderWidget: function(section_id, option_index, cfgvalue) {
+ let node = form.Value.prototype.renderWidget.apply(this, arguments);
+
+ node.classList.add('control-group');
+ node.firstChild.style.width = '30em';
+
+ node.appendChild(E('button', {
+ class: 'cbi-button cbi-button-add',
+ click: ui.createHandlerFn(this, async (section_id) => {
+ try {
+ await navigator.clipboard.writeText(this.formvalue(section_id));
+ console.log('Content copied to clipboard!');
+ } catch (e) {
+ console.error('Failed to copy: ', e);
+ }
+ /* Deprecated
+ let inputEl = document.getElementById(this.cbid(section_id)).querySelector('input');
+ inputEl.select();
+ document.execCommand("copy");
+ inputEl.blur();
+ */
+ return alert(_('Content copied to clipboard!'));
+ }, section_id)
+ }, [ _('Copy') ]));
+
+ return node;
+ },
+
+ write: function() {}
+});
+
+class VlessEncryption {
+ // origin:
+ // https://github.com/XTLS/Xray-core/pull/5067
+ // server:
+ // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L64
+ // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/server.go#L42
+ // client:
+ // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L12
+ // https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/client.go#L45
+/*
+{
+ "method": "mlkem768x25519plus",
+ "xormode": "native",
+ "ticket": "600s",
+ "rtt": "0rtt",
+ "paddings": [ // Optional
+ "100-111-1111",
+ "75-0-111",
+ "50-0-3333",
+ ...
+ ],
+ "keypairs": [
+ {
+ "type": "vless-x25519",
+ "server": "cP5Oy9MOpTaBKKE17Pfd56mbb1CIfp5EMpyBYqr2EG8",
+ "client": "khEcQMT8j41xWmGYKpZtQ4vd8_9VWyFVmmCDIhRJ-Uk"
+ },
+ {
+ "type": "vless-mlkem768",
+ "server": "UHPx3nf-FVxF95byAw0YG025aQNw9HxKej-MiG5AhTcdW_WFpHlTVYQU5NHmXP6tmljSnB2iPmSQ29fisGxEog",
+ "client": "h4sdZgCc5-ZefvQ8mZmReOWQdxYb0mwngMdl7pKhYEZZpGWHUPKAmxug87Bgj3GqSHs195QeVpxfrMLNB5Jm0Ge71Fc-A3aLpaS3C3pARbGQoquUDUEVDNwEWjQTvFpGTUV3Nddw_LlRmWN6Wqhguti9cpS6GhEmkBvBayFeHgZosuaQ1FMoAqIeQzSSSoguCZtGLUmdQjEs3zc5rwG1rNanbhtyI3QnooYvr3A0vggIkbmddjtjwYaVQdMAj9Moavc12EAUajOV91QA73RWVuhelbe7pLumsHiW-emIdBgVhEgDDYdGaLq1E8QjB0WbIfufnJp-CJa3Ieu9gmDASTlQBeEREeA9gfoZcTpYD8elhJIJxaPJKXchvUVkFhZarcivlKoqVuaFPzsJM7KQCBC8zfS0t_oiBka-uzg3_Hl153nMTDaCAbZULPZGE-p2EazI2eFBCDktdHtDffJNo7i7ZYSkWkqN9ysr2QZRvYG_PYCzcYSo34Gf5WNvHKuz0Ye3kFkckfuirCmzr3knw2azrSOmpTOX_RSlMlse7HgFYwxHPMJnzPS19ymiwKZPgrAMvCmAUZmsxZGDoKeusNEDGSSFhLcTQys20qGBGYasIgKYGjAKGjK7SCxSOCGBQSU496XBkXQEeOB7k9Sh8jdB0pQGAZw9Ntwvrts2DjIUcsQBv-XEGfnHQXoBmDgzwzYEWxeHd0oNbPIlz7CqvNseoKu6uPZl85xynum6aWd6BDDAtwobbqYkuMUfOUhXf_cH13kWSnuJ6QrOxah94JzAnda3tWRDQ3RajOOjk-OXhbOqi8QMJRFdA_C-xMwQalM_rTSTKOqyCcaNSTkVmMlmyOt90tptk7jKUizDmGhGbsSU8WMY5mhdZ3eUd5O6gQitiMHI1EqnlaRNsXnKFoJ5yHV82Wp1dhFONCG_dlpqunVJD5bFgpxtdFDD-KmXQTymAalFjxeVl_xdc5xd4XYCYmk5dhEiQBE0J_S3Z6x0tmFORpWG9lESK_OBRSul9oKZh9Vet-UZ8FSOVtNFwbeokRwWpFuFL1dL3UpJeININ2cgUfDNWQlwItkokiFf_Kdy12y2O_hqJtoTpNttNxTOiclDzKM1KHNOjYJgTgydcid3mmJl3eA6ezyrDAw1RLCHBucIvYRfwbkmpYMvnfAaA2DIiaTNaSxX8BUl92V49UVKWlQSp8ijfmmTRHrBMmxKjvBIgHqC6dSMhVUEOMzCKXAO3giCS3eZzdrNQGhhqTxpYYnFf6uLoKOIiaGY-ByI1YoIVXxX8aCTOOpesFvHjwOKBEoj4Hoxd3iFMUJQazR7P2drnfmS11kgipM7pSUgB7POKwxEF0NQCedM41wVIuoathAqD6N6qalwQ6iOKlZOBUwwMVAMRDJ3aomG37ZeLYhv6fB0-pUUJSN1q4knjtkLFIJSUrih9FZ0XnOll_aeEgOICqQkb4aOMrovjcJEWvgdjUqGPdyIGgkurfqBRHih3dukUcYxt6Y__4KLQ7acqMx0FOFv0ZxFRTCIRGj_GAlFWUi6fpuPKebXUnEn1PRE0iNXwUV_4jESWb0"
+ },
+ ...
+ ]
+}
+*/
+ constructor(payload) {
+ this.input = payload || '';
+ try {
+ let content = JSON.parse(this.input.trim());
+ Object.keys(content).forEach(key => this[key] = content[key]);
+ } catch {}
+
+ this.method ||= hm.vless_encryption.methods[0][0];
+ this.xormode ||= hm.vless_encryption.xormodes[0][0];
+ this.ticket ||= hm.vless_encryption.tickets[0][0];
+ this.rtt ||= hm.vless_encryption.rtts[0][0];
+ this.paddings ||= [];
+ this.keypairs ||= [];
+ }
+
+ setKey(key, value) {
+ this[key] = value;
+
+ return this
+ }
+
+ _toMihomo(payload, side) {
+ if (!['server', 'client'].includes(side))
+ throw new Error('Unknown side: ' + side); // `Unknown side: '${side}'`
+
+ let required = [
+ payload.method,
+ payload.xormode,
+ side === 'server' ? payload.ticket : side === 'client' ? payload.rtt : null
+ ].join('.');
+
+ return required +
+ (hm.isEmpty(payload.paddings) ? '' : '.' + payload.paddings.join('.')) + // Optional
+ (hm.isEmpty(payload.keypairs) ? '' : '.' + payload.keypairs.map(e => e[side]).join('.')); // Required
+ }
+
+ toString(format, side) {
+ format ||= 'json';
+
+ let payload = hm.removeBlankAttrs({
+ method: this.method,
+ xormode: this.xormode,
+ ticket: this.ticket,
+ rtt: this.rtt,
+ paddings: this.paddings || [],
+ keypairs: this.keypairs || []
+ });
+
+ if (format === 'json')
+ return JSON.stringify(payload);
+ else if (format === 'mihomo')
+ return this._toMihomo(payload, side);
+ else
+ throw new Error(`Unknown format: '${format}'`);
+ }
+}
+
return view.extend({
load() {
return Promise.all([
@@ -303,7 +432,210 @@ return view.extend({
o.depends('type', 'vless');
o.modalonly = true;
- hm.renderVlessEncryption(s, data[0]);
+ const initVlessEncryptionOption = function(o, key) {
+ o.load = function(section_id) {
+ return new VlessEncryption(uci.get(data[0], section_id, 'vless_encryption_hmpayload'))[key];
+ }
+ o.onchange = function(ev, section_id, value) {
+ let UIEl = this.section.getUIElement(section_id, 'vless_encryption_hmpayload');
+ let newpayload = new VlessEncryption(UIEl.getValue()).setKey(key, value);
+
+ UIEl.setValue(newpayload.toString());
+
+ [
+ ['server', '_vless_encryption_decryption'],
+ ['client', '_vless_encryption_encryption']
+ ].forEach(([side, option]) => {
+ UIEl = this.section.getUIElement(section_id, option);
+ UIEl.setValue(newpayload.toString('mihomo', side));
+ });
+ }
+ o.write = function() {};
+ }
+
+ o = s.taboption('field_vless_encryption', form.Value, 'vless_encryption_hmpayload', _('Payload'));
+ o.readonly = true;
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
+
+ o = s.taboption('field_vless_encryption', CBIDummyCopyValue, '_vless_encryption_decryption', _('decryption'));
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
+
+ o = s.taboption('field_vless_encryption', CBIDummyCopyValue, '_vless_encryption_encryption', _('encryption'));
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
+
+ o = s.taboption('field_vless_encryption', form.ListValue, 'vless_encryption_method', _('Encryption method'));
+ o.default = hm.vless_encryption.methods[0][0];
+ hm.vless_encryption.methods.forEach((res) => {
+ o.value.apply(o, res);
+ })
+ initVlessEncryptionOption(o, 'method');
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
+
+ o = s.taboption('field_vless_encryption', form.RichListValue, 'vless_encryption_xormode', _('XOR mode'));
+ o.default = hm.vless_encryption.xormodes[0][0];
+ hm.vless_encryption.xormodes.forEach((res) => {
+ o.value.apply(o, res);
+ })
+ initVlessEncryptionOption(o, 'xormode');
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
+
+ o = s.taboption('field_vless_encryption', hm.RichValue, 'vless_encryption_ticket', _('Server') +' '+ _('RTT'));
+ o.default = hm.vless_encryption.tickets[0][0];
+ hm.vless_encryption.tickets.forEach((res) => {
+ o.value.apply(o, res);
+ })
+ initVlessEncryptionOption(o, 'ticket');
+ o.validate = function(section_id, value) {
+ if (!value)
+ return true;
+
+ if (!value.match(/^(\d+-)?\d+s$/))
+ return _('Expecting: %s').format('^(\\d+-)?\\d+s$');
+
+ return true;
+ }
+ o.rmempty = false;
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
+
+ o = s.taboption('field_vless_encryption', form.ListValue, 'vless_encryption_rtt', _('Client') +' '+ _('RTT'));
+ o.default = hm.vless_encryption.rtts[0][0];
+ hm.vless_encryption.rtts.forEach((res) => {
+ o.value.apply(o, res);
+ })
+ initVlessEncryptionOption(o, 'rtt');
+ o.rmempty = false;
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
+
+ o = s.taboption('field_vless_encryption', !hm.pr7558_merged ? hm.DynamicList : form.DynamicList, 'vless_encryption_paddings', _('Paddings'), // @pr7558_merged
+ _('The server and client can set different padding parameters.') + '' +
+ _('In the order of one Padding-Length and one Padding-Interval, infinite concatenation.') + '' +
+ _('The first padding must have a probability of 100% and at least 35 bytes.'));
+ hm.vless_encryption.paddings.forEach((res) => {
+ o.value.apply(o, res);
+ })
+ initVlessEncryptionOption(o, 'paddings');
+ o.validate = function(section_id, value) {
+ if (!value)
+ return true;
+
+ if (!value.match(/^\d+(-\d+){2}$/))
+ return _('Expecting: %s').format('^\\d+(-\\d+){2}$');
+
+ return true;
+ }
+ o.allowduplicates = true;
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
+
+ o = s.taboption('field_vless_encryption', hm.GenText, 'vless_encryption_keypairs', _('Keypairs'));
+ o.placeholder = '[\n {\n "type": "vless-x25519",\n "server": "cP5Oy9MOpTaBKKE17Pfd56mbb1CIfp5EMpyBYqr2EG8",\n "client": "khEcQMT8j41xWmGYKpZtQ4vd8_9VWyFVmmCDIhRJ-Uk"\n },\n {\n "type": "vless-mlkem768",\n "server": "UHPx3nf-FVxF95byAw0YG025aQNw9HxKej-MiG5AhTcdW_WFpHlTVYQU5NHmXP6tmljSnB2iPmSQ29fisGxEog",\n "client": "h4sdZgCc5-ZefvQ8mZmReOWQdxYb0mwngMdl7pKhYEZZpGWHUPKAmxug87Bgj3GqSHs195QeVpxfrMLNB5J..."\n },\n ...\n]';
+ o.rows = 10;
+ o.hm_options = {
+ type: hm.vless_encryption.keypairs.types[0][0],
+ params: '',
+ callback: function(result) {
+ const section_id = this.section.section;
+ const key_type = this.hm_options.type;
+
+ let keypair = {"type": key_type, "server": "", "client": ""};
+ switch (key_type) {
+ case 'vless-x25519':
+ keypair.server = result.private_key;
+ keypair.client = result.password;
+ break;
+ case 'vless-mlkem768':
+ keypair.server = result.seed;
+ keypair.client = result.client;
+ break;
+ default:
+ break;
+ }
+
+ let keypairs = [];
+ try {
+ keypairs = JSON.parse(this.formvalue(section_id).trim());
+ } catch {}
+ if (!Array.isArray(keypairs))
+ keypairs = [];
+
+ keypairs.push(keypair);
+
+ return [
+ [this.option, JSON.stringify(keypairs, null, 2)]
+ ]
+ }
+ }
+ o.renderWidget = function(section_id, option_index, cfgvalue) {
+ let node = hm.TextValue.prototype.renderWidget.apply(this, arguments);
+ const cbid = this.cbid(section_id) + '._keytype_select';
+ const selected = this.hm_options.type;
+
+ let selectEl = E('select', {
+ id: cbid,
+ class: 'cbi-input-select',
+ style: 'width: 10em',
+ });
+
+ hm.vless_encryption.keypairs.types.forEach(([k, v]) => {
+ selectEl.appendChild(E('option', {
+ 'value': k,
+ 'selected': (k === selected) ? '' : null
+ }, [ v ]));
+ });
+
+ node.appendChild(E('div', { 'class': 'control-group' }, [
+ selectEl,
+ E('button', {
+ class: 'cbi-button cbi-button-add',
+ click: ui.createHandlerFn(this, () => {
+ this.hm_options.type = document.getElementById(cbid).value;
+
+ return hm.handleGenKey.call(this, this.hm_options);
+ })
+ }, [ _('Generate') ])
+ ]));
+
+ return node;
+ }
+ o.load = function(section_id) {
+ return JSON.stringify(new VlessEncryption(uci.get(data[0], section_id, 'vless_encryption_hmpayload'))['keypairs'], null, 2);
+ }
+ o.validate = function(section_id, value) {
+ let result = hm.validateJson.apply(this, arguments);
+
+ if (result === true) {
+ let keypairs = JSON.parse(value.trim());
+
+ if (Array.isArray(keypairs) && keypairs.length >= 1) {
+ let UIEl = this.section.getUIElement(section_id, 'vless_encryption_hmpayload');
+ let newpayload = new VlessEncryption(UIEl.getValue()).setKey('keypairs', keypairs);
+
+ UIEl.setValue(newpayload.toString());
+
+ [
+ ['server', '_vless_encryption_decryption'],
+ ['client', '_vless_encryption_encryption']
+ ].forEach(([side, option]) => {
+ UIEl = this.section.getUIElement(section_id, option);
+ UIEl.setValue(newpayload.toString('mihomo', side));
+ });
+ } else
+ return _('Expecting: %s').format(_('least one keypair required'));
+
+ return true;
+ } else
+ return result;
+ }
+ o.rmempty = false;
+ o.depends('vless_decryption', '1');
+ o.modalonly = true;
/* TLS fields */
o = s.taboption('field_general', form.Flag, 'tls', _('TLS'));
diff --git a/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration b/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration
index e4c094f933..e28d56ec69 100755
--- a/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration
+++ b/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration
@@ -4,9 +4,6 @@
sed -i 's|^FQDN:$|DOMAIN:|' "/etc/fchomo/resources/direct_list.yaml" 2>/dev/null
sed -i 's|^FQDN:$|DOMAIN:|' "/etc/fchomo/resources/proxy_list.yaml" 2>/dev/null
-# mieru_port_range -> mieru_ports
-sed -i 's|^\toption mieru_port_range |\tlist mieru_ports |' /etc/config/fchomo 2>/dev/null
-
# default_proxy -> client_enabled and MATCH rule
default_proxy=$(uci -q get fchomo.routing.default_proxy)
if [ -n "$default_proxy" ]; then
diff --git a/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration_node b/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration_node
new file mode 100755
index 0000000000..691f6c9f5d
--- /dev/null
+++ b/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration_node
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Migration script for fchomo node
+# Used to migrate LuCI application nodes option.
+
+. /lib/functions.sh
+. /usr/share/libubox/jshn.sh
+
+CONF=fchomo
+
+config_load "$CONF"
+
+# isDefined