mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-10 19:50:28 +08:00
Compare commits
4 Commits
9963f67433
...
a5d2114363
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5d2114363 | ||
|
|
e70ca694b9 | ||
|
|
6a9edd8665 | ||
|
|
b42287d104 |
@ -25,3 +25,15 @@ fi
|
|||||||
if hash update-desktop-database 2>/dev/null; then
|
if hash update-desktop-database 2>/dev/null; then
|
||||||
update-desktop-database /usr/share/applications || true
|
update-desktop-database /usr/share/applications || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Update icon cache for GNOME/GTK environments
|
||||||
|
if hash gtk-update-icon-cache 2>/dev/null; then
|
||||||
|
for dir in /usr/share/icons/hicolor /usr/share/icons/gnome; do
|
||||||
|
[ -d "$dir" ] && gtk-update-icon-cache -f -t "$dir" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Refresh GNOME Shell icon cache
|
||||||
|
if hash update-icon-caches 2>/dev/null; then
|
||||||
|
update-icon-caches /usr/share/icons/* 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|||||||
@ -15,6 +15,13 @@ case "$1" in
|
|||||||
if hash update-desktop-database 2>/dev/null; then
|
if hash update-desktop-database 2>/dev/null; then
|
||||||
update-desktop-database /usr/share/applications || true
|
update-desktop-database /usr/share/applications || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Update icon cache
|
||||||
|
if hash gtk-update-icon-cache 2>/dev/null; then
|
||||||
|
for dir in /usr/share/icons/hicolor /usr/share/icons/gnome; do
|
||||||
|
[ -d "$dir" ] && gtk-update-icon-cache -f -t "$dir" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# others
|
# others
|
||||||
|
|||||||
@ -59,7 +59,11 @@ linux:
|
|||||||
desktop:
|
desktop:
|
||||||
entry:
|
entry:
|
||||||
Name: Clash Party
|
Name: Clash Party
|
||||||
|
GenericName: Proxy Client
|
||||||
|
Comment: A GUI client based on Mihomo
|
||||||
MimeType: 'x-scheme-handler/clash;x-scheme-handler/mihomo'
|
MimeType: 'x-scheme-handler/clash;x-scheme-handler/mihomo'
|
||||||
|
Keywords: proxy;clash;mihomo;vpn;
|
||||||
|
StartupWMClass: clash-party
|
||||||
target:
|
target:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
const js = require('@eslint/js')
|
const js = require('@eslint/js')
|
||||||
const react = require('eslint-plugin-react')
|
const react = require('eslint-plugin-react')
|
||||||
|
const reactHooks = require('eslint-plugin-react-hooks')
|
||||||
|
const importPlugin = require('eslint-plugin-import')
|
||||||
const { configs } = require('@electron-toolkit/eslint-config-ts')
|
const { configs } = require('@electron-toolkit/eslint-config-ts')
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
ignores: ['**/node_modules/**', '**/dist/**', '**/out/**', '**/extra/**']
|
ignores: ['**/node_modules/**', '**/dist/**', '**/out/**', '**/extra/**', '**/src/native/**']
|
||||||
},
|
},
|
||||||
|
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
@ -13,11 +15,30 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||||
plugins: {
|
plugins: {
|
||||||
react: react
|
react: react,
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
import: importPlugin
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...react.configs.recommended.rules,
|
...react.configs.recommended.rules,
|
||||||
...react.configs['jsx-runtime'].rules
|
...react.configs['jsx-runtime'].rules,
|
||||||
|
// React Hooks 规则
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
// Import 规则
|
||||||
|
'import/no-duplicates': 'warn',
|
||||||
|
'import/order': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||||
|
'newlines-between': 'never'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// 代码质量
|
||||||
|
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||||
|
'no-debugger': 'warn',
|
||||||
|
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||||||
|
'prefer-const': 'warn'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
@ -45,7 +66,15 @@ module.exports = [
|
|||||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
||||||
],
|
],
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'warn'
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'warn'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
files: ['**/logger.ts'],
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
@ -8,6 +8,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||||
|
"lint:check": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
|
||||||
|
"review": "pnpm run lint:check && pnpm run typecheck",
|
||||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
||||||
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
|
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
|
||||||
@ -31,7 +33,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/utils": "^4.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
"sysproxy-rs": "file:src/native/sysproxy",
|
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"chokidar": "^5.0.0",
|
"chokidar": "^5.0.0",
|
||||||
@ -40,6 +41,7 @@
|
|||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"i18next": "^25.6.2",
|
"i18next": "^25.6.2",
|
||||||
"iconv-lite": "^0.7.1",
|
"iconv-lite": "^0.7.1",
|
||||||
|
"sysproxy-rs": "file:sysproxy-rs",
|
||||||
"webdav": "^5.8.0",
|
"webdav": "^5.8.0",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3",
|
||||||
"yaml": "^2.8.1"
|
"yaml": "^2.8.1"
|
||||||
@ -61,6 +63,8 @@
|
|||||||
"@types/react": "^19.2.5",
|
"@types/react": "^19.2.5",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
||||||
|
"@typescript-eslint/parser": "^8.52.0",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"cron-validator": "^1.4.0",
|
"cron-validator": "^1.4.0",
|
||||||
@ -71,7 +75,9 @@
|
|||||||
"electron-vite": "4.0.1",
|
"electron-vite": "4.0.1",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"eslint": "9.39.2",
|
"eslint": "9.39.2",
|
||||||
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.4",
|
||||||
"framer-motion": "12.23.26",
|
"framer-motion": "12.23.26",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
388
pnpm-lock.yaml
generated
388
pnpm-lock.yaml
generated
@ -36,8 +36,8 @@ importers:
|
|||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
sysproxy-rs:
|
sysproxy-rs:
|
||||||
specifier: file:src/native/sysproxy
|
specifier: file:sysproxy-rs
|
||||||
version: file:src/native/sysproxy
|
version: file:sysproxy-rs
|
||||||
webdav:
|
webdav:
|
||||||
specifier: ^5.8.0
|
specifier: ^5.8.0
|
||||||
version: 5.8.0
|
version: 5.8.0
|
||||||
@ -96,6 +96,12 @@ importers:
|
|||||||
'@types/ws':
|
'@types/ws':
|
||||||
specifier: ^8.18.1
|
specifier: ^8.18.1
|
||||||
version: 8.18.1
|
version: 8.18.1
|
||||||
|
'@typescript-eslint/eslint-plugin':
|
||||||
|
specifier: ^8.52.0
|
||||||
|
version: 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/parser':
|
||||||
|
specifier: ^8.52.0
|
||||||
|
version: 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^5.1.2
|
specifier: ^5.1.2
|
||||||
version: 5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))
|
version: 5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
@ -126,9 +132,15 @@ importers:
|
|||||||
eslint:
|
eslint:
|
||||||
specifier: 9.39.2
|
specifier: 9.39.2
|
||||||
version: 9.39.2(jiti@2.6.1)
|
version: 9.39.2(jiti@2.6.1)
|
||||||
|
eslint-plugin-import:
|
||||||
|
specifier: ^2.32.0
|
||||||
|
version: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))
|
||||||
eslint-plugin-react:
|
eslint-plugin-react:
|
||||||
specifier: ^7.37.5
|
specifier: ^7.37.5
|
||||||
version: 7.37.5(eslint@9.39.2(jiti@2.6.1))
|
version: 7.37.5(eslint@9.39.2(jiti@2.6.1))
|
||||||
|
eslint-plugin-react-hooks:
|
||||||
|
specifier: ^7.0.1
|
||||||
|
version: 7.0.1(eslint@9.39.2(jiti@2.6.1))
|
||||||
form-data:
|
form-data:
|
||||||
specifier: ^4.0.4
|
specifier: ^4.0.4
|
||||||
version: 4.0.5
|
version: 4.0.5
|
||||||
@ -1993,6 +2005,9 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@rtsao/scc@1.1.0':
|
||||||
|
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||||
|
|
||||||
'@sindresorhus/is@4.6.0':
|
'@sindresorhus/is@4.6.0':
|
||||||
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
|
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -2168,6 +2183,9 @@ packages:
|
|||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
|
'@types/json5@0.0.29':
|
||||||
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
|
|
||||||
'@types/keyv@3.1.4':
|
'@types/keyv@3.1.4':
|
||||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||||
|
|
||||||
@ -2235,6 +2253,14 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/eslint-plugin@8.52.0':
|
||||||
|
resolution: {integrity: sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
'@typescript-eslint/parser': ^8.52.0
|
||||||
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.51.0':
|
'@typescript-eslint/parser@8.51.0':
|
||||||
resolution: {integrity: sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==}
|
resolution: {integrity: sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@ -2242,22 +2268,45 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/parser@8.52.0':
|
||||||
|
resolution: {integrity: sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.51.0':
|
'@typescript-eslint/project-service@8.51.0':
|
||||||
resolution: {integrity: sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==}
|
resolution: {integrity: sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/project-service@8.52.0':
|
||||||
|
resolution: {integrity: sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.51.0':
|
'@typescript-eslint/scope-manager@8.51.0':
|
||||||
resolution: {integrity: sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==}
|
resolution: {integrity: sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/scope-manager@8.52.0':
|
||||||
|
resolution: {integrity: sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.51.0':
|
'@typescript-eslint/tsconfig-utils@8.51.0':
|
||||||
resolution: {integrity: sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==}
|
resolution: {integrity: sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/tsconfig-utils@8.52.0':
|
||||||
|
resolution: {integrity: sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.51.0':
|
'@typescript-eslint/type-utils@8.51.0':
|
||||||
resolution: {integrity: sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==}
|
resolution: {integrity: sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@ -2265,16 +2314,33 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/type-utils@8.52.0':
|
||||||
|
resolution: {integrity: sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/types@8.51.0':
|
'@typescript-eslint/types@8.51.0':
|
||||||
resolution: {integrity: sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==}
|
resolution: {integrity: sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/types@8.52.0':
|
||||||
|
resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.51.0':
|
'@typescript-eslint/typescript-estree@8.51.0':
|
||||||
resolution: {integrity: sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==}
|
resolution: {integrity: sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/typescript-estree@8.52.0':
|
||||||
|
resolution: {integrity: sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.51.0':
|
'@typescript-eslint/utils@8.51.0':
|
||||||
resolution: {integrity: sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==}
|
resolution: {integrity: sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@ -2282,10 +2348,21 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/utils@8.52.0':
|
||||||
|
resolution: {integrity: sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.51.0':
|
'@typescript-eslint/visitor-keys@8.51.0':
|
||||||
resolution: {integrity: sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==}
|
resolution: {integrity: sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/visitor-keys@8.52.0':
|
||||||
|
resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
|
||||||
@ -2385,6 +2462,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
|
resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
array.prototype.findlastindex@1.2.6:
|
||||||
|
resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
array.prototype.flat@1.3.3:
|
array.prototype.flat@1.3.3:
|
||||||
resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
|
resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -2722,6 +2803,14 @@ packages:
|
|||||||
dayjs@1.11.19:
|
dayjs@1.11.19:
|
||||||
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
||||||
|
|
||||||
|
debug@3.2.7:
|
||||||
|
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@ -2958,6 +3047,40 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>=7.0.0'
|
eslint: '>=7.0.0'
|
||||||
|
|
||||||
|
eslint-import-resolver-node@0.3.9:
|
||||||
|
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
|
||||||
|
|
||||||
|
eslint-module-utils@2.12.1:
|
||||||
|
resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
peerDependencies:
|
||||||
|
'@typescript-eslint/parser': '*'
|
||||||
|
eslint: '*'
|
||||||
|
eslint-import-resolver-node: '*'
|
||||||
|
eslint-import-resolver-typescript: '*'
|
||||||
|
eslint-import-resolver-webpack: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@typescript-eslint/parser':
|
||||||
|
optional: true
|
||||||
|
eslint:
|
||||||
|
optional: true
|
||||||
|
eslint-import-resolver-node:
|
||||||
|
optional: true
|
||||||
|
eslint-import-resolver-typescript:
|
||||||
|
optional: true
|
||||||
|
eslint-import-resolver-webpack:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
eslint-plugin-import@2.32.0:
|
||||||
|
resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
peerDependencies:
|
||||||
|
'@typescript-eslint/parser': '*'
|
||||||
|
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@typescript-eslint/parser':
|
||||||
|
optional: true
|
||||||
|
|
||||||
eslint-plugin-prettier@5.5.4:
|
eslint-plugin-prettier@5.5.4:
|
||||||
resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==}
|
resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
@ -2972,6 +3095,12 @@ packages:
|
|||||||
eslint-config-prettier:
|
eslint-config-prettier:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
eslint-plugin-react-hooks@7.0.1:
|
||||||
|
resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
||||||
|
|
||||||
eslint-plugin-react@7.37.5:
|
eslint-plugin-react@7.37.5:
|
||||||
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
|
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -3301,6 +3430,12 @@ packages:
|
|||||||
hast-util-whitespace@3.0.0:
|
hast-util-whitespace@3.0.0:
|
||||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||||
|
|
||||||
|
hermes-estree@0.25.1:
|
||||||
|
resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
|
||||||
|
|
||||||
|
hermes-parser@0.25.1:
|
||||||
|
resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
|
||||||
|
|
||||||
hosted-git-info@4.1.0:
|
hosted-git-info@4.1.0:
|
||||||
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
|
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -3616,6 +3751,10 @@ packages:
|
|||||||
json-stringify-safe@5.0.1:
|
json-stringify-safe@5.0.1:
|
||||||
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
|
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
|
||||||
|
|
||||||
|
json5@1.0.2:
|
||||||
|
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
json5@2.2.3:
|
json5@2.2.3:
|
||||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -4093,6 +4232,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
|
resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
object.groupby@1.0.3:
|
||||||
|
resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
object.values@1.2.1:
|
object.values@1.2.1:
|
||||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -4422,6 +4565,11 @@ packages:
|
|||||||
resolve-pkg-maps@1.0.0:
|
resolve-pkg-maps@1.0.0:
|
||||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||||
|
|
||||||
|
resolve@1.22.11:
|
||||||
|
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
resolve@2.0.0-next.5:
|
resolve@2.0.0-next.5:
|
||||||
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
|
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -4664,6 +4812,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
|
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
strip-bom@3.0.0:
|
||||||
|
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
strip-json-comments@3.1.1:
|
strip-json-comments@3.1.1:
|
||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -4698,8 +4850,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
|
resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
|
||||||
sysproxy-rs@file:src/native/sysproxy:
|
sysproxy-rs@file:sysproxy-rs:
|
||||||
resolution: {directory: src/native/sysproxy, type: directory}
|
resolution: {directory: sysproxy-rs, type: directory}
|
||||||
|
|
||||||
tailwind-merge@3.4.0:
|
tailwind-merge@3.4.0:
|
||||||
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
|
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
|
||||||
@ -4769,6 +4921,15 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4'
|
typescript: '>=4.8.4'
|
||||||
|
|
||||||
|
ts-api-utils@2.4.0:
|
||||||
|
resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
|
||||||
|
engines: {node: '>=18.12'}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4'
|
||||||
|
|
||||||
|
tsconfig-paths@3.15.0:
|
||||||
|
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
|
||||||
|
|
||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
@ -5098,6 +5259,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
zod-validation-error@4.0.2:
|
||||||
|
resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.0 || ^4.0.0
|
||||||
|
|
||||||
|
zod@4.3.5:
|
||||||
|
resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==}
|
||||||
|
|
||||||
zwitch@2.0.4:
|
zwitch@2.0.4:
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
|
|
||||||
@ -7578,6 +7748,8 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc@4.54.0':
|
'@rollup/rollup-win32-x64-msvc@4.54.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@rtsao/scc@1.1.0': {}
|
||||||
|
|
||||||
'@sindresorhus/is@4.6.0': {}
|
'@sindresorhus/is@4.6.0': {}
|
||||||
|
|
||||||
'@swc/helpers@0.5.18':
|
'@swc/helpers@0.5.18':
|
||||||
@ -7746,6 +7918,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
|
'@types/json5@0.0.29': {}
|
||||||
|
|
||||||
'@types/keyv@3.1.4':
|
'@types/keyv@3.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.0.3
|
'@types/node': 25.0.3
|
||||||
@ -7829,6 +8003,22 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@eslint-community/regexpp': 4.12.2
|
||||||
|
'@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/scope-manager': 8.52.0
|
||||||
|
'@typescript-eslint/type-utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/visitor-keys': 8.52.0
|
||||||
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
ignore: 7.0.5
|
||||||
|
natural-compare: 1.4.0
|
||||||
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.51.0
|
'@typescript-eslint/scope-manager': 8.51.0
|
||||||
@ -7841,6 +8031,18 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/scope-manager': 8.52.0
|
||||||
|
'@typescript-eslint/types': 8.52.0
|
||||||
|
'@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/visitor-keys': 8.52.0
|
||||||
|
debug: 4.4.3
|
||||||
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.51.0(typescript@5.9.3)':
|
'@typescript-eslint/project-service@8.51.0(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3)
|
||||||
@ -7850,15 +8052,33 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/project-service@8.52.0(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/types': 8.52.0
|
||||||
|
debug: 4.4.3
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.51.0':
|
'@typescript-eslint/scope-manager@8.51.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.51.0
|
'@typescript-eslint/types': 8.51.0
|
||||||
'@typescript-eslint/visitor-keys': 8.51.0
|
'@typescript-eslint/visitor-keys': 8.51.0
|
||||||
|
|
||||||
|
'@typescript-eslint/scope-manager@8.52.0':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.52.0
|
||||||
|
'@typescript-eslint/visitor-keys': 8.52.0
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.51.0(typescript@5.9.3)':
|
'@typescript-eslint/tsconfig-utils@8.51.0(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
'@typescript-eslint/tsconfig-utils@8.52.0(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/type-utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.51.0
|
'@typescript-eslint/types': 8.51.0
|
||||||
@ -7871,8 +8091,22 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/type-utils@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.52.0
|
||||||
|
'@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
|
debug: 4.4.3
|
||||||
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/types@8.51.0': {}
|
'@typescript-eslint/types@8.51.0': {}
|
||||||
|
|
||||||
|
'@typescript-eslint/types@8.52.0': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.51.0(typescript@5.9.3)':
|
'@typescript-eslint/typescript-estree@8.51.0(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/project-service': 8.51.0(typescript@5.9.3)
|
'@typescript-eslint/project-service': 8.51.0(typescript@5.9.3)
|
||||||
@ -7888,6 +8122,21 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/typescript-estree@8.52.0(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/project-service': 8.52.0(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/types': 8.52.0
|
||||||
|
'@typescript-eslint/visitor-keys': 8.52.0
|
||||||
|
debug: 4.4.3
|
||||||
|
minimatch: 9.0.5
|
||||||
|
semver: 7.7.3
|
||||||
|
tinyglobby: 0.2.15
|
||||||
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
|
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
|
||||||
@ -7899,11 +8148,27 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/utils@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
|
||||||
|
'@typescript-eslint/scope-manager': 8.52.0
|
||||||
|
'@typescript-eslint/types': 8.52.0
|
||||||
|
'@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
|
||||||
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.51.0':
|
'@typescript-eslint/visitor-keys@8.51.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.51.0
|
'@typescript-eslint/types': 8.51.0
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
|
'@typescript-eslint/visitor-keys@8.52.0':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.52.0
|
||||||
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
'@vitejs/plugin-react@5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))':
|
'@vitejs/plugin-react@5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
@ -8043,6 +8308,16 @@ snapshots:
|
|||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
es-shim-unscopables: 1.1.0
|
es-shim-unscopables: 1.1.0
|
||||||
|
|
||||||
|
array.prototype.findlastindex@1.2.6:
|
||||||
|
dependencies:
|
||||||
|
call-bind: 1.0.8
|
||||||
|
call-bound: 1.0.4
|
||||||
|
define-properties: 1.2.1
|
||||||
|
es-abstract: 1.24.1
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
es-shim-unscopables: 1.1.0
|
||||||
|
|
||||||
array.prototype.flat@1.3.3:
|
array.prototype.flat@1.3.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
@ -8415,6 +8690,10 @@ snapshots:
|
|||||||
|
|
||||||
dayjs@1.11.19: {}
|
dayjs@1.11.19: {}
|
||||||
|
|
||||||
|
debug@3.2.7:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
@ -8801,6 +9080,53 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
|
||||||
|
eslint-import-resolver-node@0.3.9:
|
||||||
|
dependencies:
|
||||||
|
debug: 3.2.7
|
||||||
|
is-core-module: 2.16.1
|
||||||
|
resolve: 1.22.11
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)):
|
||||||
|
dependencies:
|
||||||
|
debug: 3.2.7
|
||||||
|
optionalDependencies:
|
||||||
|
'@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
eslint-import-resolver-node: 0.3.9
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)):
|
||||||
|
dependencies:
|
||||||
|
'@rtsao/scc': 1.1.0
|
||||||
|
array-includes: 3.1.9
|
||||||
|
array.prototype.findlastindex: 1.2.6
|
||||||
|
array.prototype.flat: 1.3.3
|
||||||
|
array.prototype.flatmap: 1.3.3
|
||||||
|
debug: 3.2.7
|
||||||
|
doctrine: 2.1.0
|
||||||
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
eslint-import-resolver-node: 0.3.9
|
||||||
|
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))
|
||||||
|
hasown: 2.0.2
|
||||||
|
is-core-module: 2.16.1
|
||||||
|
is-glob: 4.0.3
|
||||||
|
minimatch: 3.1.2
|
||||||
|
object.fromentries: 2.0.8
|
||||||
|
object.groupby: 1.0.3
|
||||||
|
object.values: 1.2.1
|
||||||
|
semver: 6.3.1
|
||||||
|
string.prototype.trimend: 1.0.9
|
||||||
|
tsconfig-paths: 3.15.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- eslint-import-resolver-typescript
|
||||||
|
- eslint-import-resolver-webpack
|
||||||
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4):
|
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
@ -8810,6 +9136,17 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1))
|
eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1))
|
||||||
|
|
||||||
|
eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)):
|
||||||
|
dependencies:
|
||||||
|
'@babel/core': 7.28.5
|
||||||
|
'@babel/parser': 7.28.5
|
||||||
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
hermes-parser: 0.25.1
|
||||||
|
zod: 4.3.5
|
||||||
|
zod-validation-error: 4.0.2(zod@4.3.5)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)):
|
eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: 3.1.9
|
array-includes: 3.1.9
|
||||||
@ -9252,6 +9589,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
hermes-estree@0.25.1: {}
|
||||||
|
|
||||||
|
hermes-parser@0.25.1:
|
||||||
|
dependencies:
|
||||||
|
hermes-estree: 0.25.1
|
||||||
|
|
||||||
hosted-git-info@4.1.0:
|
hosted-git-info@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 6.0.0
|
lru-cache: 6.0.0
|
||||||
@ -9565,6 +9908,10 @@ snapshots:
|
|||||||
json-stringify-safe@5.0.1:
|
json-stringify-safe@5.0.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
json5@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
minimist: 1.2.8
|
||||||
|
|
||||||
json5@2.2.3: {}
|
json5@2.2.3: {}
|
||||||
|
|
||||||
jsonc-parser@3.3.1: {}
|
jsonc-parser@3.3.1: {}
|
||||||
@ -10147,6 +10494,12 @@ snapshots:
|
|||||||
es-abstract: 1.24.1
|
es-abstract: 1.24.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
object.groupby@1.0.3:
|
||||||
|
dependencies:
|
||||||
|
call-bind: 1.0.8
|
||||||
|
define-properties: 1.2.1
|
||||||
|
es-abstract: 1.24.1
|
||||||
|
|
||||||
object.values@1.2.1:
|
object.values@1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
@ -10484,6 +10837,12 @@ snapshots:
|
|||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0: {}
|
||||||
|
|
||||||
|
resolve@1.22.11:
|
||||||
|
dependencies:
|
||||||
|
is-core-module: 2.16.1
|
||||||
|
path-parse: 1.0.7
|
||||||
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
resolve@2.0.0-next.5:
|
resolve@2.0.0-next.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
@ -10825,6 +11184,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex: 6.2.2
|
ansi-regex: 6.2.2
|
||||||
|
|
||||||
|
strip-bom@3.0.0: {}
|
||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|
||||||
strnum@1.1.2: {}
|
strnum@1.1.2: {}
|
||||||
@ -10859,7 +11220,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@pkgr/core': 0.2.9
|
'@pkgr/core': 0.2.9
|
||||||
|
|
||||||
sysproxy-rs@file:src/native/sysproxy: {}
|
sysproxy-rs@file:sysproxy-rs: {}
|
||||||
|
|
||||||
tailwind-merge@3.4.0: {}
|
tailwind-merge@3.4.0: {}
|
||||||
|
|
||||||
@ -10929,6 +11290,17 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
ts-api-utils@2.4.0(typescript@5.9.3):
|
||||||
|
dependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
tsconfig-paths@3.15.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/json5': 0.0.29
|
||||||
|
json5: 1.0.2
|
||||||
|
minimist: 1.2.8
|
||||||
|
strip-bom: 3.0.0
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
tsx@4.21.0:
|
tsx@4.21.0:
|
||||||
@ -11272,4 +11644,10 @@ snapshots:
|
|||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
|
zod-validation-error@4.0.2(zod@4.3.5):
|
||||||
|
dependencies:
|
||||||
|
zod: 4.3.5
|
||||||
|
|
||||||
|
zod@4.3.5: {}
|
||||||
|
|
||||||
zwitch@2.0.4: {}
|
zwitch@2.0.4: {}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { controledMihomoConfigPath } from '../utils/dirs'
|
|
||||||
import { readFile, writeFile } from 'fs/promises'
|
import { readFile, writeFile } from 'fs/promises'
|
||||||
|
import { existsSync } from 'fs'
|
||||||
|
import { controledMihomoConfigPath } from '../utils/dirs'
|
||||||
import { parse, stringify } from '../utils/yaml'
|
import { parse, stringify } from '../utils/yaml'
|
||||||
import { generateProfile } from '../core/factory'
|
import { generateProfile } from '../core/factory'
|
||||||
import { getAppConfig } from './app'
|
|
||||||
import { defaultControledMihomoConfig } from '../utils/template'
|
import { defaultControledMihomoConfig } from '../utils/template'
|
||||||
import { deepMerge } from '../utils/merge'
|
import { deepMerge } from '../utils/merge'
|
||||||
import { existsSync } from 'fs'
|
|
||||||
import { createLogger } from '../utils/logger'
|
import { createLogger } from '../utils/logger'
|
||||||
|
import { getAppConfig } from './app'
|
||||||
|
|
||||||
const controledMihomoLogger = createLogger('ControledMihomo')
|
const controledMihomoLogger = createLogger('ControledMihomo')
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { overrideConfigPath, overridePath } from '../utils/dirs'
|
|
||||||
import { getControledMihomoConfig } from './controledMihomo'
|
|
||||||
import { readFile, writeFile, rm } from 'fs/promises'
|
import { readFile, writeFile, rm } from 'fs/promises'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
|
import { overrideConfigPath, overridePath } from '../utils/dirs'
|
||||||
import * as chromeRequest from '../utils/chromeRequest'
|
import * as chromeRequest from '../utils/chromeRequest'
|
||||||
import { parse, stringify } from '../utils/yaml'
|
import { parse, stringify } from '../utils/yaml'
|
||||||
|
import { getControledMihomoConfig } from './controledMihomo'
|
||||||
|
|
||||||
let overrideConfig: IOverrideConfig // override.yaml
|
let overrideConfig: IOverrideConfig // override.yaml
|
||||||
let overrideConfigWriteQueue: Promise<void> = Promise.resolve()
|
let overrideConfigWriteQueue: Promise<void> = Promise.resolve()
|
||||||
|
|||||||
@ -1,20 +1,19 @@
|
|||||||
import { getControledMihomoConfig } from './controledMihomo'
|
|
||||||
import { mihomoProfileWorkDir, mihomoWorkDir, profileConfigPath, profilePath } from '../utils/dirs'
|
|
||||||
import { addProfileUpdater, removeProfileUpdater } from '../core/profileUpdater'
|
|
||||||
import { readFile, rm, writeFile } from 'fs/promises'
|
import { readFile, rm, writeFile } from 'fs/promises'
|
||||||
import { restartCore } from '../core/manager'
|
|
||||||
import { getAppConfig } from './app'
|
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { app } from 'electron'
|
||||||
|
import i18next from 'i18next'
|
||||||
import * as chromeRequest from '../utils/chromeRequest'
|
import * as chromeRequest from '../utils/chromeRequest'
|
||||||
import { parse, stringify } from '../utils/yaml'
|
import { parse, stringify } from '../utils/yaml'
|
||||||
import { defaultProfile } from '../utils/template'
|
import { defaultProfile } from '../utils/template'
|
||||||
import { subStorePort } from '../resolve/server'
|
import { subStorePort } from '../resolve/server'
|
||||||
import { join } from 'path'
|
|
||||||
import { app } from 'electron'
|
|
||||||
import { mihomoUpgradeConfig } from '../core/mihomoApi'
|
import { mihomoUpgradeConfig } from '../core/mihomoApi'
|
||||||
|
import { restartCore } from '../core/manager'
|
||||||
import i18next from 'i18next'
|
import { addProfileUpdater, removeProfileUpdater } from '../core/profileUpdater'
|
||||||
|
import { mihomoProfileWorkDir, mihomoWorkDir, profileConfigPath, profilePath } from '../utils/dirs'
|
||||||
import { createLogger } from '../utils/logger'
|
import { createLogger } from '../utils/logger'
|
||||||
|
import { getAppConfig } from './app'
|
||||||
|
import { getControledMihomoConfig } from './controledMihomo'
|
||||||
|
|
||||||
const profileLogger = createLogger('Profile')
|
const profileLogger = createLogger('Profile')
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ export async function setProfileConfig(config: IProfileConfig): Promise<void> {
|
|||||||
export async function updateProfileConfig(
|
export async function updateProfileConfig(
|
||||||
updater: (config: IProfileConfig) => IProfileConfig | Promise<IProfileConfig>
|
updater: (config: IProfileConfig) => IProfileConfig | Promise<IProfileConfig>
|
||||||
): Promise<IProfileConfig> {
|
): Promise<IProfileConfig> {
|
||||||
let result: IProfileConfig
|
let result: IProfileConfig | undefined
|
||||||
profileConfigWriteQueue = profileConfigWriteQueue.then(async () => {
|
profileConfigWriteQueue = profileConfigWriteQueue.then(async () => {
|
||||||
const data = await readFile(profileConfigPath(), 'utf-8')
|
const data = await readFile(profileConfigPath(), 'utf-8')
|
||||||
profileConfig = parse(data) || { items: [] }
|
profileConfig = parse(data) || { items: [] }
|
||||||
@ -52,7 +51,7 @@ export async function updateProfileConfig(
|
|||||||
await writeFile(profileConfigPath(), stringify(profileConfig), 'utf-8')
|
await writeFile(profileConfigPath(), stringify(profileConfig), 'utf-8')
|
||||||
})
|
})
|
||||||
await profileConfigWriteQueue
|
await profileConfigWriteQueue
|
||||||
return structuredClone(result!)
|
return structuredClone(result ?? profileConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProfileItem(id: string | undefined): Promise<IProfileItem | undefined> {
|
export async function getProfileItem(id: string | undefined): Promise<IProfileItem | undefined> {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { overrideLogger } from '../utils/logger'
|
||||||
import { getAppConfig } from './app'
|
import { getAppConfig } from './app'
|
||||||
import { addOverrideItem, removeOverrideItem, getOverrideItem } from './override'
|
import { addOverrideItem, removeOverrideItem, getOverrideItem } from './override'
|
||||||
import { overrideLogger } from '../utils/logger'
|
|
||||||
|
|
||||||
const SMART_OVERRIDE_ID = 'smart-core-override'
|
const SMART_OVERRIDE_ID = 'smart-core-override'
|
||||||
|
|
||||||
|
|||||||
76
src/main/core/dns.ts
Normal file
76
src/main/core/dns.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { exec } from 'child_process'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
import { net } from 'electron'
|
||||||
|
import { getAppConfig, patchAppConfig } from '../config'
|
||||||
|
|
||||||
|
const execPromise = promisify(exec)
|
||||||
|
|
||||||
|
let setPublicDNSTimer: NodeJS.Timeout | null = null
|
||||||
|
let recoverDNSTimer: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
|
export async function getDefaultDevice(): Promise<string> {
|
||||||
|
const { stdout: deviceOut } = await execPromise(`route -n get default`)
|
||||||
|
let device = deviceOut.split('\n').find((s) => s.includes('interface:'))
|
||||||
|
device = device?.trim().split(' ').slice(1).join(' ')
|
||||||
|
if (!device) throw new Error('Get device failed')
|
||||||
|
return device
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDefaultService(): Promise<string> {
|
||||||
|
const device = await getDefaultDevice()
|
||||||
|
const { stdout: order } = await execPromise(`networksetup -listnetworkserviceorder`)
|
||||||
|
const block = order.split('\n\n').find((s) => s.includes(`Device: ${device}`))
|
||||||
|
if (!block) throw new Error('Get networkservice failed')
|
||||||
|
for (const line of block.split('\n')) {
|
||||||
|
if (line.match(/^\(\d+\).*/)) {
|
||||||
|
return line.trim().split(' ').slice(1).join(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('Get service failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOriginDNS(): Promise<void> {
|
||||||
|
const service = await getDefaultService()
|
||||||
|
const { stdout: dns } = await execPromise(`networksetup -getdnsservers "${service}"`)
|
||||||
|
if (dns.startsWith("There aren't any DNS Servers set on")) {
|
||||||
|
await patchAppConfig({ originDNS: 'Empty' })
|
||||||
|
} else {
|
||||||
|
await patchAppConfig({ originDNS: dns.trim().replace(/\n/g, ' ') })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setDNS(dns: string): Promise<void> {
|
||||||
|
const service = await getDefaultService()
|
||||||
|
// networksetup 需要 root 权限,通过 osascript 请求管理员权限执行
|
||||||
|
const shell = `networksetup -setdnsservers "${service}" ${dns}`
|
||||||
|
const command = `do shell script "${shell}" with administrator privileges`
|
||||||
|
await execPromise(`osascript -e '${command}'`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setPublicDNS(): Promise<void> {
|
||||||
|
if (process.platform !== 'darwin') return
|
||||||
|
if (net.isOnline()) {
|
||||||
|
const { originDNS } = await getAppConfig()
|
||||||
|
if (!originDNS) {
|
||||||
|
await getOriginDNS()
|
||||||
|
await setDNS('223.5.5.5')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (setPublicDNSTimer) clearTimeout(setPublicDNSTimer)
|
||||||
|
setPublicDNSTimer = setTimeout(() => setPublicDNS(), 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function recoverDNS(): Promise<void> {
|
||||||
|
if (process.platform !== 'darwin') return
|
||||||
|
if (net.isOnline()) {
|
||||||
|
const { originDNS } = await getAppConfig()
|
||||||
|
if (originDNS) {
|
||||||
|
await setDNS(originDNS)
|
||||||
|
await patchAppConfig({ originDNS: undefined })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (recoverDNSTimer) clearTimeout(recoverDNSTimer)
|
||||||
|
recoverDNSTimer = setTimeout(() => recoverDNS(), 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
import { copyFile, mkdir, writeFile, readFile } from 'fs/promises'
|
||||||
|
import vm from 'vm'
|
||||||
|
import { existsSync, writeFileSync } from 'fs'
|
||||||
|
import path from 'path'
|
||||||
import {
|
import {
|
||||||
getControledMihomoConfig,
|
getControledMihomoConfig,
|
||||||
getProfileConfig,
|
getProfileConfig,
|
||||||
@ -16,11 +20,7 @@ import {
|
|||||||
rulePath
|
rulePath
|
||||||
} from '../utils/dirs'
|
} from '../utils/dirs'
|
||||||
import { parse, stringify } from '../utils/yaml'
|
import { parse, stringify } from '../utils/yaml'
|
||||||
import { copyFile, mkdir, writeFile, readFile } from 'fs/promises'
|
|
||||||
import { deepMerge } from '../utils/merge'
|
import { deepMerge } from '../utils/merge'
|
||||||
import vm from 'vm'
|
|
||||||
import { existsSync, writeFileSync } from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import { createLogger } from '../utils/logger'
|
import { createLogger } from '../utils/logger'
|
||||||
|
|
||||||
const factoryLogger = createLogger('Factory')
|
const factoryLogger = createLogger('Factory')
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,17 @@
|
|||||||
import axios, { AxiosInstance } from 'axios'
|
import axios, { AxiosInstance } from 'axios'
|
||||||
|
import WebSocket from 'ws'
|
||||||
import { getAppConfig, getControledMihomoConfig } from '../config'
|
import { getAppConfig, getControledMihomoConfig } from '../config'
|
||||||
import { mainWindow } from '../window'
|
import { mainWindow } from '../window'
|
||||||
import WebSocket from 'ws'
|
|
||||||
import { tray } from '../resolve/tray'
|
import { tray } from '../resolve/tray'
|
||||||
import { calcTraffic } from '../utils/calc'
|
import { calcTraffic } from '../utils/calc'
|
||||||
import { getRuntimeConfig } from './factory'
|
|
||||||
import { floatingWindow } from '../resolve/floatingWindow'
|
import { floatingWindow } from '../resolve/floatingWindow'
|
||||||
import { getMihomoIpcPath } from './manager'
|
|
||||||
import { createLogger } from '../utils/logger'
|
import { createLogger } from '../utils/logger'
|
||||||
|
import { getRuntimeConfig } from './factory'
|
||||||
|
import { getMihomoIpcPath } from './manager'
|
||||||
|
|
||||||
const mihomoApiLogger = createLogger('MihomoApi')
|
const mihomoApiLogger = createLogger('MihomoApi')
|
||||||
|
|
||||||
let axiosIns: AxiosInstance = null!
|
let axiosIns: AxiosInstance | null = null
|
||||||
let currentIpcPath: string = ''
|
let currentIpcPath: string = ''
|
||||||
let mihomoTrafficWs: WebSocket | null = null
|
let mihomoTrafficWs: WebSocket | null = null
|
||||||
let trafficRetry = 10
|
let trafficRetry = 10
|
||||||
@ -27,7 +27,6 @@ const MAX_RETRY = 10
|
|||||||
export const getAxios = async (force: boolean = false): Promise<AxiosInstance> => {
|
export const getAxios = async (force: boolean = false): Promise<AxiosInstance> => {
|
||||||
const dynamicIpcPath = getMihomoIpcPath()
|
const dynamicIpcPath = getMihomoIpcPath()
|
||||||
|
|
||||||
// 如路径改变 强制重新创建实例
|
|
||||||
if (axiosIns && !force && currentIpcPath === dynamicIpcPath) {
|
if (axiosIns && !force && currentIpcPath === dynamicIpcPath) {
|
||||||
return axiosIns
|
return axiosIns
|
||||||
}
|
}
|
||||||
|
|||||||
408
src/main/core/permissions.ts
Normal file
408
src/main/core/permissions.ts
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
import { exec, execFile } from 'child_process'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
import { stat } from 'fs/promises'
|
||||||
|
import { existsSync } from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { app, dialog, ipcMain } from 'electron'
|
||||||
|
import { getAppConfig, patchControledMihomoConfig } from '../config'
|
||||||
|
import { mihomoCorePath, mihomoCoreDir } from '../utils/dirs'
|
||||||
|
import { managerLogger } from '../utils/logger'
|
||||||
|
import i18next from '../../shared/i18n'
|
||||||
|
|
||||||
|
const execPromise = promisify(exec)
|
||||||
|
const execFilePromise = promisify(execFile)
|
||||||
|
|
||||||
|
// 内核名称白名单
|
||||||
|
const ALLOWED_CORES = ['mihomo', 'mihomo-alpha', 'mihomo-smart'] as const
|
||||||
|
type AllowedCore = (typeof ALLOWED_CORES)[number]
|
||||||
|
|
||||||
|
export function isValidCoreName(core: string): core is AllowedCore {
|
||||||
|
return ALLOWED_CORES.includes(core as AllowedCore)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateCorePath(corePath: string): void {
|
||||||
|
if (corePath.includes('..')) {
|
||||||
|
throw new Error('Invalid core path: directory traversal detected')
|
||||||
|
}
|
||||||
|
|
||||||
|
const dangerousChars = /[;&|`$(){}[\]<>'"\\]/
|
||||||
|
if (dangerousChars.test(path.basename(corePath))) {
|
||||||
|
throw new Error('Invalid core path: contains dangerous characters')
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedPath = path.normalize(path.resolve(corePath))
|
||||||
|
const expectedDir = path.normalize(path.resolve(mihomoCoreDir()))
|
||||||
|
|
||||||
|
if (!normalizedPath.startsWith(expectedDir + path.sep) && normalizedPath !== expectedDir) {
|
||||||
|
throw new Error('Invalid core path: not in expected directory')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shellEscape(arg: string): string {
|
||||||
|
return "'" + arg.replace(/'/g, "'\\''") + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 会话管理员状态缓存
|
||||||
|
let sessionAdminStatus: boolean | null = null
|
||||||
|
|
||||||
|
export async function initAdminStatus(): Promise<void> {
|
||||||
|
if (process.platform === 'win32' && sessionAdminStatus === null) {
|
||||||
|
sessionAdminStatus = await checkAdminPrivileges().catch(() => false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSessionAdminStatus(): boolean {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return sessionAdminStatus ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkAdminPrivileges(): Promise<boolean> {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await execPromise('chcp 65001 >nul 2>&1 && fltmc', { encoding: 'utf8' })
|
||||||
|
managerLogger.info('Admin privileges confirmed via fltmc')
|
||||||
|
return true
|
||||||
|
} catch (fltmcError: unknown) {
|
||||||
|
const errorCode = (fltmcError as { code?: number })?.code || 0
|
||||||
|
managerLogger.debug(`fltmc failed with code ${errorCode}, trying net session as fallback`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await execPromise('chcp 65001 >nul 2>&1 && net session', { encoding: 'utf8' })
|
||||||
|
managerLogger.info('Admin privileges confirmed via net session')
|
||||||
|
return true
|
||||||
|
} catch (netSessionError: unknown) {
|
||||||
|
const netErrorCode = (netSessionError as { code?: number })?.code || 0
|
||||||
|
managerLogger.debug(
|
||||||
|
`Both fltmc and net session failed, no admin privileges. Error codes: fltmc=${errorCode}, net=${netErrorCode}`
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkMihomoCorePermissions(): Promise<boolean> {
|
||||||
|
const { core = 'mihomo' } = await getAppConfig()
|
||||||
|
const corePath = mihomoCorePath(core)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
return await checkAdminPrivileges()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
|
const stats = await stat(corePath)
|
||||||
|
return (stats.mode & 0o4000) !== 0 && stats.uid === 0
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkHighPrivilegeCore(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const { core = 'mihomo' } = await getAppConfig()
|
||||||
|
const corePath = mihomoCorePath(core)
|
||||||
|
|
||||||
|
managerLogger.info(`Checking high privilege core: ${corePath}`)
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
if (!existsSync(corePath)) {
|
||||||
|
managerLogger.info('Core file does not exist')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasHighPrivilegeProcess = await checkHighPrivilegeMihomoProcess()
|
||||||
|
if (hasHighPrivilegeProcess) {
|
||||||
|
managerLogger.info('Found high privilege mihomo process running')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdmin = await checkAdminPrivileges()
|
||||||
|
managerLogger.info(`Current process admin privileges: ${isAdmin}`)
|
||||||
|
return isAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
|
managerLogger.info('Non-Windows platform, skipping high privilege core check')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.error('Failed to check high privilege core', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkHighPrivilegeMihomoProcess(): Promise<boolean> {
|
||||||
|
const mihomoExecutables =
|
||||||
|
process.platform === 'win32'
|
||||||
|
? ['mihomo.exe', 'mihomo-alpha.exe', 'mihomo-smart.exe']
|
||||||
|
: ['mihomo', 'mihomo-alpha', 'mihomo-smart']
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
for (const executable of mihomoExecutables) {
|
||||||
|
try {
|
||||||
|
const { stdout } = await execPromise(
|
||||||
|
`chcp 65001 >nul 2>&1 && tasklist /FI "IMAGENAME eq ${executable}" /FO CSV`,
|
||||||
|
{ encoding: 'utf8' }
|
||||||
|
)
|
||||||
|
const lines = stdout.split('\n').filter((line) => line.includes(executable))
|
||||||
|
|
||||||
|
if (lines.length > 0) {
|
||||||
|
managerLogger.info(`Found ${lines.length} ${executable} processes running`)
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const parts = line.split(',')
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
const pid = parts[1].replace(/"/g, '').trim()
|
||||||
|
try {
|
||||||
|
const { stdout: processInfo } = await execPromise(
|
||||||
|
`powershell -NoProfile -Command "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Process -Id ${pid} | Select-Object Name,Id,Path,CommandLine | ConvertTo-Json"`,
|
||||||
|
{ encoding: 'utf8' }
|
||||||
|
)
|
||||||
|
const processJson = JSON.parse(processInfo)
|
||||||
|
managerLogger.info(`Process ${pid} info: ${processInfo.substring(0, 200)}`)
|
||||||
|
|
||||||
|
if (processJson.Name.includes('mihomo') && processJson.Path === null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
managerLogger.info(`Cannot get info for process ${pid}, might be high privilege`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.error(`Failed to check ${executable} processes`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let foundProcesses = false
|
||||||
|
|
||||||
|
for (const executable of mihomoExecutables) {
|
||||||
|
try {
|
||||||
|
const { stdout } = await execPromise(`ps aux | grep ${executable} | grep -v grep`)
|
||||||
|
const lines = stdout.split('\n').filter((line) => line.trim() && line.includes(executable))
|
||||||
|
|
||||||
|
if (lines.length > 0) {
|
||||||
|
foundProcesses = true
|
||||||
|
managerLogger.info(`Found ${lines.length} ${executable} processes running`)
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const parts = line.trim().split(/\s+/)
|
||||||
|
if (parts.length >= 1) {
|
||||||
|
const user = parts[0]
|
||||||
|
managerLogger.info(`${executable} process running as user: ${user}`)
|
||||||
|
|
||||||
|
if (user === 'root') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundProcesses) {
|
||||||
|
managerLogger.info('No mihomo processes found running')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.error('Failed to check high privilege mihomo process', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function grantTunPermissions(): Promise<void> {
|
||||||
|
const { core = 'mihomo' } = await getAppConfig()
|
||||||
|
|
||||||
|
if (!isValidCoreName(core)) {
|
||||||
|
throw new Error(`Invalid core name: ${core}. Allowed values: ${ALLOWED_CORES.join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const corePath = mihomoCorePath(core)
|
||||||
|
validateCorePath(corePath)
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
const escapedPath = shellEscape(corePath)
|
||||||
|
const script = `do shell script "chown root:admin ${escapedPath} && chmod +sx ${escapedPath}" with administrator privileges`
|
||||||
|
await execFilePromise('osascript', ['-e', script])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
await execFilePromise('pkexec', ['chown', 'root:root', corePath])
|
||||||
|
await execFilePromise('pkexec', ['chmod', '+sx', corePath])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
throw new Error('Windows platform requires running as administrator')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function restartAsAdmin(forTun: boolean = true): Promise<void> {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
throw new Error('This function is only available on Windows')
|
||||||
|
}
|
||||||
|
|
||||||
|
const exePath = process.execPath
|
||||||
|
const args = process.argv.slice(1)
|
||||||
|
const restartArgs = forTun ? [...args, '--admin-restart-for-tun'] : args
|
||||||
|
|
||||||
|
try {
|
||||||
|
const escapedExePath = exePath.replace(/'/g, "''")
|
||||||
|
const argsString = restartArgs.map((arg) => arg.replace(/'/g, "''")).join("', '")
|
||||||
|
|
||||||
|
const command =
|
||||||
|
restartArgs.length > 0
|
||||||
|
? `powershell -NoProfile -Command "Start-Process -FilePath '${escapedExePath}' -ArgumentList '${argsString}' -Verb RunAs"`
|
||||||
|
: `powershell -NoProfile -Command "Start-Process -FilePath '${escapedExePath}' -Verb RunAs"`
|
||||||
|
|
||||||
|
managerLogger.info('Restarting as administrator with command', command)
|
||||||
|
|
||||||
|
exec(command, { windowsHide: true }, (error, _stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
managerLogger.error('PowerShell execution error', error)
|
||||||
|
managerLogger.error('stderr', stderr)
|
||||||
|
} else {
|
||||||
|
managerLogger.info('PowerShell command executed successfully')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.quit()
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.error('Failed to restart as administrator', error)
|
||||||
|
throw new Error(`Failed to restart as administrator: ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestTunPermissions(): Promise<void> {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
await restartAsAdmin()
|
||||||
|
} else {
|
||||||
|
const hasPermissions = await checkMihomoCorePermissions()
|
||||||
|
if (!hasPermissions) {
|
||||||
|
await grantTunPermissions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function showTunPermissionDialog(): Promise<boolean> {
|
||||||
|
managerLogger.info('Preparing TUN permission dialog...')
|
||||||
|
|
||||||
|
const title = i18next.t('tun.permissions.title') || '需要管理员权限'
|
||||||
|
const message =
|
||||||
|
i18next.t('tun.permissions.message') ||
|
||||||
|
'启用 TUN 模式需要管理员权限,是否现在重启应用获取权限?'
|
||||||
|
const confirmText = i18next.t('common.confirm') || '确认'
|
||||||
|
const cancelText = i18next.t('common.cancel') || '取消'
|
||||||
|
|
||||||
|
const choice = dialog.showMessageBoxSync({
|
||||||
|
type: 'warning',
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
buttons: [confirmText, cancelText],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
managerLogger.info(`TUN permission dialog choice: ${choice}`)
|
||||||
|
return choice === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function showErrorDialog(title: string, message: string): Promise<void> {
|
||||||
|
const okText = i18next.t('common.confirm') || '确认'
|
||||||
|
|
||||||
|
dialog.showMessageBoxSync({
|
||||||
|
type: 'error',
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
buttons: [okText],
|
||||||
|
defaultId: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateTunPermissionsOnStartup(_restartCore: () => Promise<void>): Promise<void> {
|
||||||
|
const { getControledMihomoConfig } = await import('../config')
|
||||||
|
const { tun } = await getControledMihomoConfig()
|
||||||
|
|
||||||
|
if (!tun?.enable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPermissions = await checkMihomoCorePermissions()
|
||||||
|
|
||||||
|
if (!hasPermissions) {
|
||||||
|
managerLogger.warn('TUN is enabled but insufficient permissions detected, prompting user...')
|
||||||
|
const confirmed = await showTunPermissionDialog()
|
||||||
|
if (confirmed) {
|
||||||
|
await restartAsAdmin()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
managerLogger.warn('User declined admin restart, auto-disabling TUN...')
|
||||||
|
await patchControledMihomoConfig({ tun: { enable: false } })
|
||||||
|
|
||||||
|
const { mainWindow } = await import('../index')
|
||||||
|
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||||
|
ipcMain.emit('updateTrayMenu')
|
||||||
|
|
||||||
|
managerLogger.info('TUN auto-disabled due to insufficient permissions')
|
||||||
|
} else {
|
||||||
|
managerLogger.info('TUN permissions validated successfully')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkAdminRestartForTun(restartCore: () => Promise<void>): Promise<void> {
|
||||||
|
if (process.argv.includes('--admin-restart-for-tun')) {
|
||||||
|
managerLogger.info('Detected admin restart for TUN mode, auto-enabling TUN...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const hasAdminPrivileges = await checkAdminPrivileges()
|
||||||
|
if (hasAdminPrivileges) {
|
||||||
|
await patchControledMihomoConfig({ tun: { enable: true }, dns: { enable: true } })
|
||||||
|
|
||||||
|
const { checkAutoRun, enableAutoRun } = await import('../sys/autoRun')
|
||||||
|
const autoRunEnabled = await checkAutoRun()
|
||||||
|
if (autoRunEnabled) {
|
||||||
|
await enableAutoRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
await restartCore()
|
||||||
|
|
||||||
|
managerLogger.info('TUN mode auto-enabled after admin restart')
|
||||||
|
|
||||||
|
const { mainWindow } = await import('../index')
|
||||||
|
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||||
|
ipcMain.emit('updateTrayMenu')
|
||||||
|
} else {
|
||||||
|
managerLogger.warn('Admin restart detected but no admin privileges found')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.error('Failed to auto-enable TUN after admin restart', error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await validateTunPermissionsOnStartup(restartCore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkTunPermissions(): Promise<boolean> {
|
||||||
|
return checkMihomoCorePermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function manualGrantCorePermition(): Promise<void> {
|
||||||
|
return grantTunPermissions()
|
||||||
|
}
|
||||||
139
src/main/core/process.ts
Normal file
139
src/main/core/process.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { exec } from 'child_process'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
import { rm } from 'fs/promises'
|
||||||
|
import { existsSync } from 'fs'
|
||||||
|
import { managerLogger } from '../utils/logger'
|
||||||
|
import { getAxios } from './mihomoApi'
|
||||||
|
|
||||||
|
const execPromise = promisify(exec)
|
||||||
|
|
||||||
|
// 常量
|
||||||
|
const CORE_READY_MAX_RETRIES = 30
|
||||||
|
const CORE_READY_RETRY_INTERVAL_MS = 500
|
||||||
|
|
||||||
|
export async function cleanupSocketFile(): Promise<void> {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
await cleanupWindowsNamedPipes()
|
||||||
|
} else {
|
||||||
|
await cleanupUnixSockets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cleanupWindowsNamedPipes(): Promise<void> {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
const { stdout } = await execPromise(
|
||||||
|
`powershell -NoProfile -Command "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Process | Where-Object {$_.ProcessName -like '*mihomo*'} | Select-Object Id,ProcessName | ConvertTo-Json"`,
|
||||||
|
{ encoding: 'utf8' }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (stdout.trim()) {
|
||||||
|
managerLogger.info(`Found potential pipe-blocking processes: ${stdout}`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const processes = JSON.parse(stdout)
|
||||||
|
const processArray = Array.isArray(processes) ? processes : [processes]
|
||||||
|
|
||||||
|
for (const proc of processArray) {
|
||||||
|
const pid = proc.Id
|
||||||
|
if (pid && pid !== process.pid) {
|
||||||
|
await terminateProcess(pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (parseError) {
|
||||||
|
managerLogger.warn('Failed to parse process list JSON:', parseError)
|
||||||
|
await fallbackTextParsing(stdout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.warn('Failed to check mihomo processes:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.error('Windows named pipe cleanup failed:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function terminateProcess(pid: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
process.kill(pid, 0)
|
||||||
|
process.kill(pid, 'SIGTERM')
|
||||||
|
managerLogger.info(`Terminated process ${pid} to free pipe`)
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if ((error as { code?: string })?.code !== 'ESRCH') {
|
||||||
|
managerLogger.warn(`Failed to terminate process ${pid}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fallbackTextParsing(stdout: string): Promise<void> {
|
||||||
|
const lines = stdout.split('\n').filter((line) => line.includes('mihomo'))
|
||||||
|
for (const line of lines) {
|
||||||
|
const match = line.match(/(\d+)/)
|
||||||
|
if (match) {
|
||||||
|
const pid = parseInt(match[1])
|
||||||
|
if (pid !== process.pid) {
|
||||||
|
await terminateProcess(pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cleanupUnixSockets(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const socketPaths = [
|
||||||
|
'/tmp/mihomo-party.sock',
|
||||||
|
'/tmp/mihomo-party-admin.sock',
|
||||||
|
`/tmp/mihomo-party-${process.getuid?.() || 'user'}.sock`
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const socketPath of socketPaths) {
|
||||||
|
try {
|
||||||
|
if (existsSync(socketPath)) {
|
||||||
|
await rm(socketPath)
|
||||||
|
managerLogger.info(`Cleaned up socket file: ${socketPath}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.warn(`Failed to cleanup socket file ${socketPath}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.error('Unix socket cleanup failed:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateWindowsPipeAccess(pipePath: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
managerLogger.info(`Validating pipe access for: ${pipePath}`)
|
||||||
|
managerLogger.info(`Pipe validation completed for: ${pipePath}`)
|
||||||
|
} catch (error) {
|
||||||
|
managerLogger.error('Windows pipe validation failed:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitForCoreReady(): Promise<void> {
|
||||||
|
for (let i = 0; i < CORE_READY_MAX_RETRIES; i++) {
|
||||||
|
try {
|
||||||
|
const axios = await getAxios(true)
|
||||||
|
await axios.get('/')
|
||||||
|
managerLogger.info(
|
||||||
|
`Core ready after ${i + 1} attempts (${(i + 1) * CORE_READY_RETRY_INTERVAL_MS}ms)`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
} catch {
|
||||||
|
if (i === 0) {
|
||||||
|
managerLogger.info('Waiting for core to be ready...')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === CORE_READY_MAX_RETRIES - 1) {
|
||||||
|
managerLogger.warn(
|
||||||
|
`Core not ready after ${CORE_READY_MAX_RETRIES} attempts, proceeding anyway`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, CORE_READY_RETRY_INTERVAL_MS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { addProfileItem, getCurrentProfileItem, getProfileConfig, getProfileItem } from '../config'
|
|
||||||
import { Cron } from 'croner'
|
import { Cron } from 'croner'
|
||||||
|
import { addProfileItem, getCurrentProfileItem, getProfileConfig, getProfileItem } from '../config'
|
||||||
import { logger } from '../utils/logger'
|
import { logger } from '../utils/logger'
|
||||||
|
|
||||||
const intervalPool: Record<string, Cron | NodeJS.Timeout> = {}
|
const intervalPool: Record<string, Cron | NodeJS.Timeout> = {}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
||||||
import { app, dialog } from 'electron'
|
import { app, dialog } from 'electron'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
import { initI18n } from '../shared/i18n'
|
||||||
import { registerIpcMainHandlers } from './utils/ipc'
|
import { registerIpcMainHandlers } from './utils/ipc'
|
||||||
import { getAppConfig, patchAppConfig } from './config'
|
import { getAppConfig, patchAppConfig } from './config'
|
||||||
import {
|
import {
|
||||||
@ -16,13 +18,8 @@ import { initShortcut } from './resolve/shortcut'
|
|||||||
import { initProfileUpdater } from './core/profileUpdater'
|
import { initProfileUpdater } from './core/profileUpdater'
|
||||||
import { startMonitor } from './resolve/trafficMonitor'
|
import { startMonitor } from './resolve/trafficMonitor'
|
||||||
import { showFloatingWindow } from './resolve/floatingWindow'
|
import { showFloatingWindow } from './resolve/floatingWindow'
|
||||||
import { initI18n } from '../shared/i18n'
|
import { logger, createLogger } from './utils/logger'
|
||||||
import i18next from 'i18next'
|
|
||||||
import { logger } from './utils/logger'
|
|
||||||
import { createLogger } from './utils/logger'
|
|
||||||
import { initWebdavBackupScheduler } from './resolve/backup'
|
import { initWebdavBackupScheduler } from './resolve/backup'
|
||||||
|
|
||||||
const mainLogger = createLogger('Main')
|
|
||||||
import {
|
import {
|
||||||
createWindow,
|
createWindow,
|
||||||
mainWindow,
|
mainWindow,
|
||||||
@ -38,6 +35,8 @@ import {
|
|||||||
getSystemLanguage
|
getSystemLanguage
|
||||||
} from './lifecycle'
|
} from './lifecycle'
|
||||||
|
|
||||||
|
const mainLogger = createLogger('Main')
|
||||||
|
|
||||||
export { mainWindow, showMainWindow, triggerMainWindow, closeMainWindow }
|
export { mainWindow, showMainWindow, triggerMainWindow, closeMainWindow }
|
||||||
|
|
||||||
const gotTheLock = app.requestSingleInstanceLock()
|
const gotTheLock = app.requestSingleInstanceLock()
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { app, powerMonitor } from 'electron'
|
|
||||||
import { spawn, exec } from 'child_process'
|
import { spawn, exec } from 'child_process'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { stat } from 'fs/promises'
|
import { stat } from 'fs/promises'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
|
import { app, powerMonitor } from 'electron'
|
||||||
import { stopCore } from './core/manager'
|
import { stopCore } from './core/manager'
|
||||||
import { triggerSysProxy } from './sys/sysproxy'
|
import { triggerSysProxy } from './sys/sysproxy'
|
||||||
import { exePath } from './utils/dirs'
|
import { exePath } from './utils/dirs'
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import * as chromeRequest from '../utils/chromeRequest'
|
|
||||||
import { parse } from '../utils/yaml'
|
|
||||||
import { app, shell } from 'electron'
|
|
||||||
import { getControledMihomoConfig } from '../config'
|
|
||||||
import { dataDir, exeDir, exePath, isPortable, resourcesFilesDir } from '../utils/dirs'
|
|
||||||
import { copyFile, rm, writeFile } from 'fs/promises'
|
import { copyFile, rm, writeFile } from 'fs/promises'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import { exec, execSync, spawn } from 'child_process'
|
import { exec, execSync, spawn } from 'child_process'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { appLogger } from '../utils/logger'
|
import { app, shell } from 'electron'
|
||||||
import { checkAdminPrivileges } from '../core/manager'
|
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
|
import { appLogger } from '../utils/logger'
|
||||||
|
import { dataDir, exeDir, exePath, isPortable, resourcesFilesDir } from '../utils/dirs'
|
||||||
|
import { getControledMihomoConfig } from '../config'
|
||||||
|
import { checkAdminPrivileges } from '../core/manager'
|
||||||
|
import { parse } from '../utils/yaml'
|
||||||
|
import * as chromeRequest from '../utils/chromeRequest'
|
||||||
|
|
||||||
export async function checkUpdate(): Promise<IAppVersion | undefined> {
|
export async function checkUpdate(): Promise<IAppVersion | undefined> {
|
||||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { getAppConfig } from '../config'
|
import https from 'https'
|
||||||
|
import { existsSync } from 'fs'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import AdmZip from 'adm-zip'
|
import AdmZip from 'adm-zip'
|
||||||
import https from 'https'
|
import { Cron } from 'croner'
|
||||||
|
import { dialog } from 'electron'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
import { systemLogger } from '../utils/logger'
|
||||||
import {
|
import {
|
||||||
appConfigPath,
|
appConfigPath,
|
||||||
controledMihomoConfigPath,
|
controledMihomoConfigPath,
|
||||||
@ -14,11 +18,7 @@ import {
|
|||||||
subStoreDir,
|
subStoreDir,
|
||||||
themesDir
|
themesDir
|
||||||
} from '../utils/dirs'
|
} from '../utils/dirs'
|
||||||
import { systemLogger } from '../utils/logger'
|
import { getAppConfig } from '../config'
|
||||||
import { Cron } from 'croner'
|
|
||||||
import { dialog } from 'electron'
|
|
||||||
import { existsSync } from 'fs'
|
|
||||||
import i18next from 'i18next'
|
|
||||||
|
|
||||||
let backupCronJob: Cron | null = null
|
let backupCronJob: Cron | null = null
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
|
import { join } from 'path'
|
||||||
import { is } from '@electron-toolkit/utils'
|
import { is } from '@electron-toolkit/utils'
|
||||||
import { BrowserWindow, ipcMain } from 'electron'
|
import { BrowserWindow, ipcMain } from 'electron'
|
||||||
import windowStateKeeper from 'electron-window-state'
|
import windowStateKeeper from 'electron-window-state'
|
||||||
import { join } from 'path'
|
|
||||||
import { getAppConfig, patchAppConfig } from '../config'
|
import { getAppConfig, patchAppConfig } from '../config'
|
||||||
|
import { floatingWindowLogger } from '../utils/logger'
|
||||||
import { applyTheme } from './theme'
|
import { applyTheme } from './theme'
|
||||||
import { buildContextMenu, showTrayIcon } from './tray'
|
import { buildContextMenu, showTrayIcon } from './tray'
|
||||||
import { floatingWindowLogger } from '../utils/logger'
|
|
||||||
|
|
||||||
export let floatingWindow: BrowserWindow | null = null
|
export let floatingWindow: BrowserWindow | null = null
|
||||||
|
|
||||||
@ -49,7 +49,9 @@ async function createFloatingWindow(): Promise<void> {
|
|||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
windowOptions.hasShadow = !safeMode
|
windowOptions.hasShadow = !safeMode
|
||||||
windowOptions.webPreferences!.offscreen = false
|
if (windowOptions.webPreferences) {
|
||||||
|
windowOptions.webPreferences.offscreen = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
floatingWindow = new BrowserWindow(windowOptions)
|
floatingWindow = new BrowserWindow(windowOptions)
|
||||||
@ -68,7 +70,9 @@ async function createFloatingWindow(): Promise<void> {
|
|||||||
})
|
})
|
||||||
|
|
||||||
floatingWindow.on('moved', () => {
|
floatingWindow.on('moved', () => {
|
||||||
floatingWindow && floatingWindowState.saveState(floatingWindow)
|
if (floatingWindow) {
|
||||||
|
floatingWindowState.saveState(floatingWindow)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// IPC 监听器
|
// IPC 监听器
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
import { getAppConfig, getControledMihomoConfig } from '../config'
|
|
||||||
import { Worker } from 'worker_threads'
|
import { Worker } from 'worker_threads'
|
||||||
import { dataDir, mihomoWorkDir, subStoreDir, substoreLogPath } from '../utils/dirs'
|
|
||||||
import subStoreIcon from '../../../resources/subStoreIcon.png?asset'
|
|
||||||
import { createWriteStream, existsSync, mkdirSync } from 'fs'
|
import { createWriteStream, existsSync, mkdirSync } from 'fs'
|
||||||
import { writeFile, rm, cp } from 'fs/promises'
|
import { writeFile, rm, cp } from 'fs/promises'
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
@ -9,8 +6,11 @@ import net from 'net'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { nativeImage } from 'electron'
|
import { nativeImage } from 'electron'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import * as chromeRequest from '../utils/chromeRequest'
|
|
||||||
import AdmZip from 'adm-zip'
|
import AdmZip from 'adm-zip'
|
||||||
|
import * as chromeRequest from '../utils/chromeRequest'
|
||||||
|
import subStoreIcon from '../../../resources/subStoreIcon.png?asset'
|
||||||
|
import { dataDir, mihomoWorkDir, subStoreDir, substoreLogPath } from '../utils/dirs'
|
||||||
|
import { getAppConfig, getControledMihomoConfig } from '../config'
|
||||||
import { systemLogger } from '../utils/logger'
|
import { systemLogger } from '../utils/logger'
|
||||||
|
|
||||||
export let pacPort: number
|
export let pacPort: number
|
||||||
|
|||||||
@ -9,9 +9,9 @@ import {
|
|||||||
import { triggerSysProxy } from '../sys/sysproxy'
|
import { triggerSysProxy } from '../sys/sysproxy'
|
||||||
import { patchMihomoConfig } from '../core/mihomoApi'
|
import { patchMihomoConfig } from '../core/mihomoApi'
|
||||||
import { quitWithoutCore, restartCore } from '../core/manager'
|
import { quitWithoutCore, restartCore } from '../core/manager'
|
||||||
|
import i18next from '../../shared/i18n'
|
||||||
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
||||||
import { updateTrayIcon } from './tray'
|
import { updateTrayIcon } from './tray'
|
||||||
import i18next from '../../shared/i18n'
|
|
||||||
|
|
||||||
export async function registerShortcut(
|
export async function registerShortcut(
|
||||||
oldShortcut: string,
|
oldShortcut: string,
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { copyFile, readdir, readFile, writeFile } from 'fs/promises'
|
import { copyFile, readdir, readFile, writeFile } from 'fs/promises'
|
||||||
import { themesDir } from '../utils/dirs'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import * as chromeRequest from '../utils/chromeRequest'
|
|
||||||
import AdmZip from 'adm-zip'
|
|
||||||
import { getControledMihomoConfig } from '../config'
|
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
|
import AdmZip from 'adm-zip'
|
||||||
|
import { t } from 'i18next'
|
||||||
|
import { themesDir } from '../utils/dirs'
|
||||||
|
import * as chromeRequest from '../utils/chromeRequest'
|
||||||
|
import { getControledMihomoConfig } from '../config'
|
||||||
import { mainWindow } from '../window'
|
import { mainWindow } from '../window'
|
||||||
import { floatingWindow } from './floatingWindow'
|
import { floatingWindow } from './floatingWindow'
|
||||||
import { t } from 'i18next'
|
|
||||||
|
|
||||||
let insertedCSSKeyMain: string | undefined = undefined
|
let insertedCSSKeyMain: string | undefined = undefined
|
||||||
let insertedCSSKeyFloating: string | undefined = undefined
|
let insertedCSSKeyFloating: string | undefined = undefined
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { ChildProcess, spawn } from 'child_process'
|
import { ChildProcess, spawn } from 'child_process'
|
||||||
import { getAppConfig } from '../config'
|
|
||||||
import { dataDir, resourcesFilesDir } from '../utils/dirs'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import { readFile, rm, writeFile } from 'fs/promises'
|
import { readFile, rm, writeFile } from 'fs/promises'
|
||||||
|
import { dataDir, resourcesFilesDir } from '../utils/dirs'
|
||||||
|
import { getAppConfig } from '../config'
|
||||||
|
|
||||||
let child: ChildProcess
|
let child: ChildProcess
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
|
||||||
|
import { t } from 'i18next'
|
||||||
import {
|
import {
|
||||||
changeCurrentProfile,
|
changeCurrentProfile,
|
||||||
getAppConfig,
|
getAppConfig,
|
||||||
@ -24,7 +26,6 @@ import {
|
|||||||
calculateTrayIconStatus
|
calculateTrayIconStatus
|
||||||
} from '../core/mihomoApi'
|
} from '../core/mihomoApi'
|
||||||
import { mainWindow, showMainWindow, triggerMainWindow } from '../window'
|
import { mainWindow, showMainWindow, triggerMainWindow } from '../window'
|
||||||
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
|
|
||||||
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
|
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
|
||||||
import { triggerSysProxy } from '../sys/sysproxy'
|
import { triggerSysProxy } from '../sys/sysproxy'
|
||||||
import {
|
import {
|
||||||
@ -34,9 +35,8 @@ import {
|
|||||||
requestTunPermissions,
|
requestTunPermissions,
|
||||||
restartAsAdmin
|
restartAsAdmin
|
||||||
} from '../core/manager'
|
} from '../core/manager'
|
||||||
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
|
||||||
import { t } from 'i18next'
|
|
||||||
import { trayLogger } from '../utils/logger'
|
import { trayLogger } from '../utils/logger'
|
||||||
|
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
||||||
|
|
||||||
export let tray: Tray | null = null
|
export let tray: Tray | null = null
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { exePath, homeDir } from '../utils/dirs'
|
|
||||||
import { tmpdir } from 'os'
|
import { tmpdir } from 'os'
|
||||||
import { mkdir, readFile, rm, writeFile } from 'fs/promises'
|
import { mkdir, readFile, rm, writeFile } from 'fs/promises'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { exePath, homeDir } from '../utils/dirs'
|
||||||
import { managerLogger } from '../utils/logger'
|
import { managerLogger } from '../utils/logger'
|
||||||
|
|
||||||
const appName = 'mihomo-party'
|
const appName = 'mihomo-party'
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { exec, execFile, spawn } from 'child_process'
|
import { exec, execFile, spawn } from 'child_process'
|
||||||
import { app, dialog, nativeTheme, shell } from 'electron'
|
|
||||||
import { readFile } from 'fs/promises'
|
import { readFile } from 'fs/promises'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
|
import { app, dialog, nativeTheme, shell } from 'electron'
|
||||||
|
import i18next from 'i18next'
|
||||||
import {
|
import {
|
||||||
dataDir,
|
dataDir,
|
||||||
exePath,
|
exePath,
|
||||||
@ -11,7 +12,6 @@ import {
|
|||||||
profilePath,
|
profilePath,
|
||||||
resourcesDir
|
resourcesDir
|
||||||
} from '../utils/dirs'
|
} from '../utils/dirs'
|
||||||
import i18next from 'i18next'
|
|
||||||
|
|
||||||
export function getFilePath(ext: string[]): string[] | undefined {
|
export function getFilePath(ext: string[]): string[] | undefined {
|
||||||
return dialog.showOpenDialogSync({
|
return dialog.showOpenDialogSync({
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
|
import { ipcMain, net } from 'electron'
|
||||||
import { getAppConfig, patchControledMihomoConfig } from '../config'
|
import { getAppConfig, patchControledMihomoConfig } from '../config'
|
||||||
import { patchMihomoConfig } from '../core/mihomoApi'
|
import { patchMihomoConfig } from '../core/mihomoApi'
|
||||||
import { mainWindow } from '../window'
|
import { mainWindow } from '../window'
|
||||||
import { ipcMain, net } from 'electron'
|
|
||||||
import { getDefaultDevice } from '../core/manager'
|
import { getDefaultDevice } from '../core/manager'
|
||||||
|
|
||||||
export async function getCurrentSSID(): Promise<string | undefined> {
|
export async function getCurrentSSID(): Promise<string | undefined> {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { triggerAutoProxy, triggerManualProxy } from 'sysproxy-rs'
|
|
||||||
import { getAppConfig, getControledMihomoConfig } from '../config'
|
|
||||||
import { pacPort, startPacServer, stopPacServer } from '../resolve/server'
|
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { triggerAutoProxy, triggerManualProxy } from 'sysproxy-rs'
|
||||||
import { net } from 'electron'
|
import { net } from 'electron'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import fs from 'fs'
|
import { getAppConfig, getControledMihomoConfig } from '../config'
|
||||||
|
import { pacPort, startPacServer, stopPacServer } from '../resolve/server'
|
||||||
import { proxyLogger } from '../utils/logger'
|
import { proxyLogger } from '../utils/logger'
|
||||||
|
|
||||||
let triggerSysProxyTimer: NodeJS.Timeout | null = null
|
let triggerSysProxyTimer: NodeJS.Timeout | null = null
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export function calcTraffic(byte: number): string {
|
|||||||
function formatNumString(num: number): string {
|
function formatNumString(num: number): string {
|
||||||
let str = num.toFixed(2)
|
let str = num.toFixed(2)
|
||||||
if (str.length <= 5) return str
|
if (str.length <= 5) return str
|
||||||
if (str.length == 6) {
|
if (str.length === 6) {
|
||||||
str = num.toFixed(1)
|
str = num.toFixed(1)
|
||||||
return str
|
return str
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { is } from '@electron-toolkit/utils'
|
|
||||||
import { existsSync, mkdirSync } from 'fs'
|
import { existsSync, mkdirSync } from 'fs'
|
||||||
import { app } from 'electron'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { is } from '@electron-toolkit/utils'
|
||||||
|
import { app } from 'electron'
|
||||||
|
|
||||||
export const homeDir = app.getPath('home')
|
export const homeDir = app.getPath('home')
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import * as chromeRequest from './chromeRequest'
|
import { createWriteStream, createReadStream , existsSync, rmSync } from 'fs'
|
||||||
import { createWriteStream, createReadStream } from 'fs'
|
|
||||||
import { writeFile } from 'fs/promises'
|
import { writeFile } from 'fs/promises'
|
||||||
import { mihomoCoreDir } from './dirs'
|
|
||||||
import AdmZip from 'adm-zip'
|
|
||||||
import { execSync } from 'child_process'
|
import { execSync } from 'child_process'
|
||||||
import { platform } from 'os'
|
import { platform } from 'os'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { existsSync, rmSync } from 'fs'
|
|
||||||
import { createGunzip } from 'zlib'
|
import { createGunzip } from 'zlib'
|
||||||
|
import AdmZip from 'adm-zip'
|
||||||
import { stopCore } from '../core/manager'
|
import { stopCore } from '../core/manager'
|
||||||
|
import { mihomoCoreDir } from './dirs'
|
||||||
|
import * as chromeRequest from './chromeRequest'
|
||||||
import { createLogger } from './logger'
|
import { createLogger } from './logger'
|
||||||
|
|
||||||
const log = createLogger('GitHub')
|
const log = createLogger('GitHub')
|
||||||
@ -60,9 +59,8 @@ export async function getGitHubTags(
|
|||||||
|
|
||||||
// 检查缓存
|
// 检查缓存
|
||||||
if (!forceRefresh && versionCache.has(cacheKey)) {
|
if (!forceRefresh && versionCache.has(cacheKey)) {
|
||||||
const cache = versionCache.get(cacheKey)!
|
const cache = versionCache.get(cacheKey)
|
||||||
// 检查缓存是否过期
|
if (cache && Date.now() - cache.timestamp < CACHE_EXPIRY) {
|
||||||
if (Date.now() - cache.timestamp < CACHE_EXPIRY) {
|
|
||||||
log.debug(`Returning cached tags for ${owner}/${repo}`)
|
log.debug(`Returning cached tags for ${owner}/${repo}`)
|
||||||
return cache.data
|
return cache.data
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import * as chromeRequest from './chromeRequest'
|
|
||||||
import { getControledMihomoConfig } from '../config'
|
import { getControledMihomoConfig } from '../config'
|
||||||
|
import * as chromeRequest from './chromeRequest'
|
||||||
|
|
||||||
export async function getImageDataURL(url: string): Promise<string> {
|
export async function getImageDataURL(url: string): Promise<string> {
|
||||||
const { 'mixed-port': port = 7890 } = await getControledMihomoConfig()
|
const { 'mixed-port': port = 7890 } = await getControledMihomoConfig()
|
||||||
|
|||||||
@ -1,3 +1,31 @@
|
|||||||
|
import { mkdir, writeFile, rm, readdir, cp, stat, rename } from 'fs/promises'
|
||||||
|
import { existsSync } from 'fs'
|
||||||
|
import { exec } from 'child_process'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
import path from 'path'
|
||||||
|
import { app, dialog } from 'electron'
|
||||||
|
import {
|
||||||
|
startPacServer,
|
||||||
|
startSubStoreBackendServer,
|
||||||
|
startSubStoreFrontendServer
|
||||||
|
} from '../resolve/server'
|
||||||
|
import { triggerSysProxy } from '../sys/sysproxy'
|
||||||
|
import {
|
||||||
|
getAppConfig,
|
||||||
|
getControledMihomoConfig,
|
||||||
|
patchAppConfig,
|
||||||
|
patchControledMihomoConfig
|
||||||
|
} from '../config'
|
||||||
|
import { startSSIDCheck } from '../sys/ssid'
|
||||||
|
import i18next, { resources } from '../../shared/i18n'
|
||||||
|
import { stringify } from './yaml'
|
||||||
|
import {
|
||||||
|
defaultConfig,
|
||||||
|
defaultControledMihomoConfig,
|
||||||
|
defaultOverrideConfig,
|
||||||
|
defaultProfile,
|
||||||
|
defaultProfileConfig
|
||||||
|
} from './template'
|
||||||
import {
|
import {
|
||||||
appConfigPath,
|
appConfigPath,
|
||||||
controledMihomoConfigPath,
|
controledMihomoConfigPath,
|
||||||
@ -15,34 +43,6 @@ import {
|
|||||||
subStoreDir,
|
subStoreDir,
|
||||||
themesDir
|
themesDir
|
||||||
} from './dirs'
|
} from './dirs'
|
||||||
import {
|
|
||||||
defaultConfig,
|
|
||||||
defaultControledMihomoConfig,
|
|
||||||
defaultOverrideConfig,
|
|
||||||
defaultProfile,
|
|
||||||
defaultProfileConfig
|
|
||||||
} from './template'
|
|
||||||
import { stringify } from './yaml'
|
|
||||||
import { mkdir, writeFile, rm, readdir, cp, stat, rename } from 'fs/promises'
|
|
||||||
import { existsSync } from 'fs'
|
|
||||||
import { exec } from 'child_process'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import path from 'path'
|
|
||||||
import {
|
|
||||||
startPacServer,
|
|
||||||
startSubStoreBackendServer,
|
|
||||||
startSubStoreFrontendServer
|
|
||||||
} from '../resolve/server'
|
|
||||||
import { triggerSysProxy } from '../sys/sysproxy'
|
|
||||||
import {
|
|
||||||
getAppConfig,
|
|
||||||
getControledMihomoConfig,
|
|
||||||
patchAppConfig,
|
|
||||||
patchControledMihomoConfig
|
|
||||||
} from '../config'
|
|
||||||
import { app, dialog } from 'electron'
|
|
||||||
import { startSSIDCheck } from '../sys/ssid'
|
|
||||||
import i18next, { resources } from '../../shared/i18n'
|
|
||||||
import { initLogger } from './logger'
|
import { initLogger } from './logger'
|
||||||
|
|
||||||
let isInitBasicCompleted = false
|
let isInitBasicCompleted = false
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import v8 from 'v8'
|
||||||
|
import { readFile, writeFile } from 'fs/promises'
|
||||||
import { app, ipcMain } from 'electron'
|
import { app, ipcMain } from 'electron'
|
||||||
|
import i18next from 'i18next'
|
||||||
import {
|
import {
|
||||||
mihomoChangeProxy,
|
mihomoChangeProxy,
|
||||||
mihomoCloseAllConnections,
|
mihomoCloseAllConnections,
|
||||||
@ -23,7 +27,6 @@ import {
|
|||||||
mihomoSmartFlushCache
|
mihomoSmartFlushCache
|
||||||
} from '../core/mihomoApi'
|
} from '../core/mihomoApi'
|
||||||
import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun'
|
import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun'
|
||||||
import { installMihomoCore, getGitHubTags, clearVersionCache } from './github'
|
|
||||||
import {
|
import {
|
||||||
getAppConfig,
|
getAppConfig,
|
||||||
patchAppConfig,
|
patchAppConfig,
|
||||||
@ -114,16 +117,13 @@ import {
|
|||||||
writeTheme
|
writeTheme
|
||||||
} from '../resolve/theme'
|
} from '../resolve/theme'
|
||||||
import { subStoreCollections, subStoreSubs } from '../core/subStoreApi'
|
import { subStoreCollections, subStoreSubs } from '../core/subStoreApi'
|
||||||
import { logDir, rulePath } from './dirs'
|
|
||||||
import path from 'path'
|
|
||||||
import v8 from 'v8'
|
|
||||||
import { getGistUrl } from '../resolve/gistApi'
|
import { getGistUrl } from '../resolve/gistApi'
|
||||||
import { getImageDataURL } from './image'
|
|
||||||
import { startMonitor } from '../resolve/trafficMonitor'
|
import { startMonitor } from '../resolve/trafficMonitor'
|
||||||
import { closeFloatingWindow, showContextMenu, showFloatingWindow } from '../resolve/floatingWindow'
|
import { closeFloatingWindow, showContextMenu, showFloatingWindow } from '../resolve/floatingWindow'
|
||||||
import i18next from 'i18next'
|
|
||||||
import { addProfileUpdater, removeProfileUpdater } from '../core/profileUpdater'
|
import { addProfileUpdater, removeProfileUpdater } from '../core/profileUpdater'
|
||||||
import { readFile, writeFile } from 'fs/promises'
|
import { getImageDataURL } from './image'
|
||||||
|
import { logDir, rulePath } from './dirs'
|
||||||
|
import { installMihomoCore, getGitHubTags, clearVersionCache } from './github'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type AsyncFn = (...args: any[]) => Promise<any>
|
type AsyncFn = (...args: any[]) => Promise<any>
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
|
import { join } from 'path'
|
||||||
import { BrowserWindow, Menu, shell } from 'electron'
|
import { BrowserWindow, Menu, shell } from 'electron'
|
||||||
import { is } from '@electron-toolkit/utils'
|
import { is } from '@electron-toolkit/utils'
|
||||||
import windowStateKeeper from 'electron-window-state'
|
import windowStateKeeper from 'electron-window-state'
|
||||||
import { join } from 'path'
|
import icon from '../../resources/icon.png?asset'
|
||||||
import { getAppConfig } from './config'
|
import { getAppConfig } from './config'
|
||||||
import { quitWithoutCore, stopCore } from './core/manager'
|
import { quitWithoutCore, stopCore } from './core/manager'
|
||||||
import { triggerSysProxy } from './sys/sysproxy'
|
import { triggerSysProxy } from './sys/sysproxy'
|
||||||
import { hideDockIcon, showDockIcon } from './resolve/tray'
|
import { hideDockIcon, showDockIcon } from './resolve/tray'
|
||||||
import icon from '../../resources/icon.png?asset'
|
|
||||||
|
|
||||||
export let mainWindow: BrowserWindow | null = null
|
export let mainWindow: BrowserWindow | null = null
|
||||||
let quitTimeout: NodeJS.Timeout | null = null
|
let quitTimeout: NodeJS.Timeout | null = null
|
||||||
|
|||||||
@ -191,7 +191,7 @@ const electronAPI = {
|
|||||||
if (!listenerMap.has(channel)) {
|
if (!listenerMap.has(channel)) {
|
||||||
listenerMap.set(channel, new Set())
|
listenerMap.set(channel, new Set())
|
||||||
}
|
}
|
||||||
listenerMap.get(channel)!.add(listener)
|
listenerMap.get(channel)?.add(listener)
|
||||||
ipcRenderer.on(channel, listener)
|
ipcRenderer.on(channel, listener)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { NavigateFunction, useLocation, useNavigate, useRoutes } from 'react-router-dom'
|
import { NavigateFunction, useLocation, useNavigate, useRoutes } from 'react-router-dom'
|
||||||
import OutboundModeSwitcher from '@renderer/components/sider/outbound-mode-switcher'
|
import OutboundModeSwitcher from '@renderer/components/sider/outbound-mode-switcher'
|
||||||
import SysproxySwitcher from '@renderer/components/sider/sysproxy-switcher'
|
import SysproxySwitcher from '@renderer/components/sider/sysproxy-switcher'
|
||||||
@ -32,10 +32,10 @@ import { applyTheme, setNativeTheme, setTitleBarOverlay } from '@renderer/utils/
|
|||||||
import { platform } from '@renderer/utils/init'
|
import { platform } from '@renderer/utils/init'
|
||||||
import { TitleBarOverlayOptions } from 'electron'
|
import { TitleBarOverlayOptions } from 'electron'
|
||||||
import SubStoreCard from '@renderer/components/sider/substore-card'
|
import SubStoreCard from '@renderer/components/sider/substore-card'
|
||||||
import MihomoIcon from './components/base/mihomo-icon'
|
|
||||||
import { createTourDriver, getDriver, startTourIfNeeded } from '@renderer/utils/tour'
|
import { createTourDriver, getDriver, startTourIfNeeded } from '@renderer/utils/tour'
|
||||||
import 'driver.js/dist/driver.css'
|
import 'driver.js/dist/driver.css'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import MihomoIcon from './components/base/mihomo-icon'
|
||||||
|
|
||||||
let navigate: NavigateFunction
|
let navigate: NavigateFunction
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ const App: React.FC = () => {
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const page = useRoutes(routes)
|
const page = useRoutes(routes)
|
||||||
|
|
||||||
const setTitlebar = (): void => {
|
const setTitlebar = useCallback((): void => {
|
||||||
if (!useWindowFrame && platform !== 'darwin') {
|
if (!useWindowFrame && platform !== 'darwin') {
|
||||||
const options = { height: 48 } as TitleBarOverlayOptions
|
const options = { height: 48 } as TitleBarOverlayOptions
|
||||||
try {
|
try {
|
||||||
@ -89,7 +89,7 @@ const App: React.FC = () => {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}, [useWindowFrame])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOrder(siderOrder)
|
setOrder(siderOrder)
|
||||||
@ -101,6 +101,13 @@ const App: React.FC = () => {
|
|||||||
resizingRef.current = resizing
|
resizingRef.current = resizing
|
||||||
}, [siderWidthValue, resizing])
|
}, [siderWidthValue, resizing])
|
||||||
|
|
||||||
|
const onResizeEnd = useCallback((): void => {
|
||||||
|
if (resizingRef.current) {
|
||||||
|
setResizing(false)
|
||||||
|
patchAppConfig({ siderWidth: siderWidthValueRef.current })
|
||||||
|
}
|
||||||
|
}, [patchAppConfig])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!tourInitialized.current) {
|
if (!tourInitialized.current) {
|
||||||
tourInitialized.current = true
|
tourInitialized.current = true
|
||||||
@ -113,25 +120,18 @@ const App: React.FC = () => {
|
|||||||
setNativeTheme(appTheme)
|
setNativeTheme(appTheme)
|
||||||
setTheme(appTheme)
|
setTheme(appTheme)
|
||||||
setTitlebar()
|
setTitlebar()
|
||||||
}, [appTheme, systemTheme])
|
}, [appTheme, systemTheme, setTheme, setTitlebar])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
applyTheme(customTheme || 'default.css').then(() => {
|
applyTheme(customTheme || 'default.css').then(() => {
|
||||||
setTitlebar()
|
setTitlebar()
|
||||||
})
|
})
|
||||||
}, [customTheme])
|
}, [customTheme, setTitlebar])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('mouseup', onResizeEnd)
|
window.addEventListener('mouseup', onResizeEnd)
|
||||||
return (): void => window.removeEventListener('mouseup', onResizeEnd)
|
return (): void => window.removeEventListener('mouseup', onResizeEnd)
|
||||||
}, [])
|
}, [onResizeEnd])
|
||||||
|
|
||||||
const onResizeEnd = (): void => {
|
|
||||||
if (resizingRef.current) {
|
|
||||||
setResizing(false)
|
|
||||||
patchAppConfig({ siderWidth: siderWidthValueRef.current })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDragEnd = async (event: DragEndEvent): Promise<void> => {
|
const onDragEnd = async (event: DragEndEvent): Promise<void> => {
|
||||||
const { active, over } = event
|
const { active, over } = event
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import * as monaco from 'monaco-editor'
|
import * as monaco from 'monaco-editor'
|
||||||
import MonacoEditor from 'react-monaco-editor'
|
import MonacoEditor from 'react-monaco-editor'
|
||||||
import { configureMonacoYaml } from 'monaco-yaml'
|
import { configureMonacoYaml } from 'monaco-yaml'
|
||||||
@ -6,7 +6,6 @@ import metaSchema from 'meta-json-schema/schemas/meta-json-schema.json'
|
|||||||
import pac from 'types-pac/pac.d.ts?raw'
|
import pac from 'types-pac/pac.d.ts?raw'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
import React from 'react'
|
|
||||||
type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
|
type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@ -36,7 +36,7 @@ const BasePage = forwardRef<HTMLDivElement, Props>((props, ref) => {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [useWindowFrame])
|
||||||
|
|
||||||
const contentRef = useRef<HTMLDivElement>(null)
|
const contentRef = useRef<HTMLDivElement>(null)
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Divider } from '@heroui/react'
|
import { Divider } from '@heroui/react'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@ -11,11 +11,11 @@ import {
|
|||||||
DropdownItem
|
DropdownItem
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
import dayjs from '@renderer/utils/dayjs'
|
import dayjs from '@renderer/utils/dayjs'
|
||||||
import { BiCopy } from 'react-icons/bi'
|
import { BiCopy } from 'react-icons/bi'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
connection: IMihomoConnectionDetail
|
connection: IMihomoConnectionDetail
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { BaseEditor } from '../base/base-editor'
|
|
||||||
import { getOverride, restartCore, setOverride } from '@renderer/utils/ipc'
|
import { getOverride, restartCore, setOverride } from '@renderer/utils/ipc'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { BaseEditor } from '../base/base-editor'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string
|
id: string
|
||||||
@ -15,13 +15,12 @@ const EditFileModal: React.FC<Props> = (props) => {
|
|||||||
const [currData, setCurrData] = useState('')
|
const [currData, setCurrData] = useState('')
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const getContent = async (): Promise<void> => {
|
|
||||||
setCurrData(await getOverride(id, language === 'javascript' ? 'js' : 'yaml'))
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getContent()
|
const loadContent = async (): Promise<void> => {
|
||||||
}, [])
|
setCurrData(await getOverride(id, language === 'javascript' ? 'js' : 'yaml'))
|
||||||
|
}
|
||||||
|
loadContent()
|
||||||
|
}, [id, language])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -9,9 +9,9 @@ import {
|
|||||||
Switch
|
Switch
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { restartCore } from '@renderer/utils/ipc'
|
import { restartCore } from '@renderer/utils/ipc'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: IOverrideItem
|
item: IOverrideItem
|
||||||
|
|||||||
@ -20,13 +20,12 @@ const ExecLogModal: React.FC<Props> = (props) => {
|
|||||||
const [logs, setLogs] = useState<string[]>([])
|
const [logs, setLogs] = useState<string[]>([])
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const getLog = async (): Promise<void> => {
|
|
||||||
setLogs((await getOverride(id, 'log')).split('\n').filter(Boolean))
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getLog()
|
const loadLog = async (): Promise<void> => {
|
||||||
}, [])
|
setLogs((await getOverride(id, 'log')).split('\n').filter(Boolean))
|
||||||
|
}
|
||||||
|
loadLog()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -12,13 +12,13 @@ import { toast } from '@renderer/components/base/toast'
|
|||||||
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
||||||
import dayjs from '@renderer/utils/dayjs'
|
import dayjs from '@renderer/utils/dayjs'
|
||||||
import React, { Key, useMemo, useState } from 'react'
|
import React, { Key, useMemo, useState } from 'react'
|
||||||
import EditFileModal from './edit-file-modal'
|
|
||||||
import EditInfoModal from './edit-info-modal'
|
|
||||||
import { useSortable } from '@dnd-kit/sortable'
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import ExecLogModal from './exec-log-modal'
|
|
||||||
import { openFile, restartCore } from '@renderer/utils/ipc'
|
import { openFile, restartCore } from '@renderer/utils/ipc'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import ExecLogModal from './exec-log-modal'
|
||||||
|
import EditInfoModal from './edit-info-modal'
|
||||||
|
import EditFileModal from './edit-file-modal'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
info: IOverrideItem
|
info: IOverrideItem
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { BaseEditor } from '../base/base-editor'
|
|
||||||
import { getProfileStr, setProfileStr } from '@renderer/utils/ipc'
|
import { getProfileStr, setProfileStr } from '@renderer/utils/ipc'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { BaseEditor } from '../base/base-editor'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string
|
id: string
|
||||||
@ -16,13 +16,12 @@ const EditFileModal: React.FC<Props> = (props) => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const getContent = async (): Promise<void> => {
|
|
||||||
setCurrData(await getProfileStr(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getContent()
|
const loadContent = async (): Promise<void> => {
|
||||||
}, [])
|
setCurrData(await getProfileStr(id))
|
||||||
|
}
|
||||||
|
loadContent()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -15,13 +15,13 @@ import {
|
|||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { useOverrideConfig } from '@renderer/hooks/use-override-config'
|
import { useOverrideConfig } from '@renderer/hooks/use-override-config'
|
||||||
import { restartCore, addProfileUpdater } from '@renderer/utils/ipc'
|
import { restartCore, addProfileUpdater } from '@renderer/utils/ipc'
|
||||||
import { MdDeleteForever } from 'react-icons/md'
|
import { MdDeleteForever } from 'react-icons/md'
|
||||||
import { FaPlus } from 'react-icons/fa6'
|
import { FaPlus } from 'react-icons/fa6'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { isValidCron } from 'cron-validator'
|
import { isValidCron } from 'cron-validator'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: IProfileItem
|
item: IProfileItem
|
||||||
|
|||||||
@ -558,188 +558,266 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const deferredFilteredRules = useDeferredValue(filteredRules)
|
const deferredFilteredRules = useDeferredValue(filteredRules)
|
||||||
|
|
||||||
const getContent = async (): Promise<void> => {
|
// 解析规则字符串
|
||||||
setIsLoading(true)
|
const parseRuleString = useCallback((ruleStr: string): RuleItem => {
|
||||||
try {
|
const parts = ruleStr.split(',')
|
||||||
const content = await getProfileStr(id)
|
const firstPartIsNumber =
|
||||||
setProfileContent(content)
|
!isNaN(Number(parts[0])) && parts[0].trim() !== '' && parts.length >= 3
|
||||||
|
|
||||||
const parsed = yaml.load(content) as Record<string, unknown> | undefined
|
let offset = 0
|
||||||
let initialRules: RuleItem[] = []
|
let ruleParts = parts
|
||||||
|
|
||||||
if (parsed && parsed.rules && Array.isArray(parsed.rules)) {
|
if (firstPartIsNumber) {
|
||||||
initialRules = parsed.rules.map((rule: string) => {
|
offset = parseInt(parts[0])
|
||||||
const parts = rule.split(',')
|
ruleParts = parts.slice(1)
|
||||||
if (parts[0] === 'MATCH') {
|
}
|
||||||
return {
|
|
||||||
type: 'MATCH',
|
if (ruleParts[0] === 'MATCH') {
|
||||||
payload: '',
|
return {
|
||||||
proxy: parts[1]
|
type: 'MATCH',
|
||||||
}
|
payload: '',
|
||||||
|
proxy: ruleParts[1],
|
||||||
|
offset: offset > 0 ? offset : undefined
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const additionalParams = ruleParts.slice(3).filter((param) => param.trim() !== '') || []
|
||||||
|
return {
|
||||||
|
type: ruleParts[0],
|
||||||
|
payload: ruleParts[1],
|
||||||
|
proxy: ruleParts[2],
|
||||||
|
additionalParams,
|
||||||
|
offset: offset > 0 ? offset : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 处理前置规则位置
|
||||||
|
const processRulesWithPositions = useCallback(
|
||||||
|
(
|
||||||
|
rulesToProcess: RuleItem[],
|
||||||
|
allRules: RuleItem[],
|
||||||
|
positionCalculator: (rule: RuleItem, currentRules: RuleItem[]) => number
|
||||||
|
): { updatedRules: RuleItem[]; ruleIndices: Set<number> } => {
|
||||||
|
const updatedRules = [...allRules]
|
||||||
|
const ruleIndices = new Set<number>()
|
||||||
|
|
||||||
|
rulesToProcess.forEach((rule) => {
|
||||||
|
const targetPosition = positionCalculator(rule, updatedRules)
|
||||||
|
const actualPosition = Math.min(targetPosition, updatedRules.length)
|
||||||
|
updatedRules.splice(actualPosition, 0, rule)
|
||||||
|
|
||||||
|
const newRuleIndices = new Set<number>()
|
||||||
|
ruleIndices.forEach((idx) => {
|
||||||
|
if (idx >= actualPosition) {
|
||||||
|
newRuleIndices.add(idx + 1)
|
||||||
} else {
|
} else {
|
||||||
const additionalParams = parts.slice(3).filter((param) => param.trim() !== '') || []
|
newRuleIndices.add(idx)
|
||||||
return {
|
|
||||||
type: parts[0],
|
|
||||||
payload: parts[1],
|
|
||||||
proxy: parts[2],
|
|
||||||
additionalParams
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
newRuleIndices.add(actualPosition)
|
||||||
|
|
||||||
// 提取代理组
|
ruleIndices.clear()
|
||||||
if (parsed) {
|
newRuleIndices.forEach((idx) => ruleIndices.add(idx))
|
||||||
const groups: string[] = []
|
})
|
||||||
|
|
||||||
// 添加代理组和代理名称
|
return { updatedRules, ruleIndices }
|
||||||
if (Array.isArray(parsed['proxy-groups'])) {
|
},
|
||||||
groups.push(
|
[]
|
||||||
...((parsed['proxy-groups'] as Array<Record<string, unknown>>)
|
)
|
||||||
.map((group) =>
|
|
||||||
group && typeof group['name'] === 'string' ? (group['name'] as string) : ''
|
|
||||||
)
|
|
||||||
.filter(Boolean) as string[])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(parsed['proxies'])) {
|
// 处理后置规则位置
|
||||||
groups.push(
|
const processAppendRulesWithPositions = useCallback(
|
||||||
...((parsed['proxies'] as Array<Record<string, unknown>>)
|
(
|
||||||
.map((proxy) =>
|
rulesToProcess: RuleItem[],
|
||||||
proxy && typeof proxy['name'] === 'string' ? (proxy['name'] as string) : ''
|
allRules: RuleItem[],
|
||||||
)
|
positionCalculator: (rule: RuleItem, currentRules: RuleItem[]) => number
|
||||||
.filter(Boolean) as string[])
|
): { updatedRules: RuleItem[]; ruleIndices: Set<number> } => {
|
||||||
)
|
const updatedRules = [...allRules]
|
||||||
}
|
const ruleIndices = new Set<number>()
|
||||||
|
|
||||||
// 预置出站 https://wiki.metacubex.one/config/proxies/built-in/
|
rulesToProcess.forEach((rule) => {
|
||||||
groups.push('DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE')
|
const targetPosition = positionCalculator(rule, updatedRules)
|
||||||
|
const actualPosition = Math.min(targetPosition, updatedRules.length)
|
||||||
|
updatedRules.splice(actualPosition, 0, rule)
|
||||||
|
|
||||||
// 去重
|
const newRuleIndices = new Set<number>()
|
||||||
setProxyGroups([...new Set(groups)])
|
ruleIndices.forEach((idx) => {
|
||||||
}
|
if (idx >= actualPosition) {
|
||||||
|
newRuleIndices.add(idx + 1)
|
||||||
|
} else {
|
||||||
|
newRuleIndices.add(idx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
newRuleIndices.add(actualPosition)
|
||||||
|
|
||||||
// 读取规则文件
|
ruleIndices.clear()
|
||||||
|
newRuleIndices.forEach((idx) => ruleIndices.add(idx))
|
||||||
|
})
|
||||||
|
|
||||||
|
return { updatedRules, ruleIndices }
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadContent = async (): Promise<void> => {
|
||||||
|
setIsLoading(true)
|
||||||
try {
|
try {
|
||||||
const ruleContent = await getRuleStr(id)
|
const content = await getProfileStr(id)
|
||||||
const ruleData = yaml.load(ruleContent) as {
|
setProfileContent(content)
|
||||||
prepend?: string[]
|
|
||||||
append?: string[]
|
const parsed = yaml.load(content) as Record<string, unknown> | undefined
|
||||||
delete?: string[]
|
let initialRules: RuleItem[] = []
|
||||||
|
|
||||||
|
if (parsed && parsed.rules && Array.isArray(parsed.rules)) {
|
||||||
|
initialRules = parsed.rules.map((rule: string) => {
|
||||||
|
const parts = rule.split(',')
|
||||||
|
if (parts[0] === 'MATCH') {
|
||||||
|
return {
|
||||||
|
type: 'MATCH',
|
||||||
|
payload: '',
|
||||||
|
proxy: parts[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const additionalParams = parts.slice(3).filter((param) => param.trim() !== '') || []
|
||||||
|
return {
|
||||||
|
type: parts[0],
|
||||||
|
payload: parts[1],
|
||||||
|
proxy: parts[2],
|
||||||
|
additionalParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ruleData) {
|
if (parsed) {
|
||||||
let allRules = [...initialRules]
|
const groups: string[] = []
|
||||||
const newPrependRules = new Set<number>()
|
|
||||||
const newAppendRules = new Set<number>()
|
|
||||||
const newDeletedRules = new Set<number>()
|
|
||||||
|
|
||||||
// 处理前置规则
|
if (Array.isArray(parsed['proxy-groups'])) {
|
||||||
if (ruleData.prepend && Array.isArray(ruleData.prepend)) {
|
groups.push(
|
||||||
const prependRules: RuleItem[] = []
|
...((parsed['proxy-groups'] as Array<Record<string, unknown>>)
|
||||||
ruleData.prepend.forEach((ruleStr: string) => {
|
.map((group) =>
|
||||||
prependRules.push(parseRuleString(ruleStr))
|
group && typeof group['name'] === 'string' ? (group['name'] as string) : ''
|
||||||
})
|
)
|
||||||
|
.filter(Boolean) as string[])
|
||||||
// 插入前置规则
|
|
||||||
const { updatedRules, ruleIndices } = processRulesWithPositions(
|
|
||||||
prependRules,
|
|
||||||
allRules,
|
|
||||||
(rule, currentRules) => {
|
|
||||||
if (rule.offset !== undefined && rule.offset < currentRules.length) {
|
|
||||||
return rule.offset
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
allRules = updatedRules
|
|
||||||
ruleIndices.forEach((index) => newPrependRules.add(index))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理后置规则
|
if (Array.isArray(parsed['proxies'])) {
|
||||||
if (ruleData.append && Array.isArray(ruleData.append)) {
|
groups.push(
|
||||||
const appendRules: RuleItem[] = []
|
...((parsed['proxies'] as Array<Record<string, unknown>>)
|
||||||
ruleData.append.forEach((ruleStr: string) => {
|
.map((proxy) =>
|
||||||
appendRules.push(parseRuleString(ruleStr))
|
proxy && typeof proxy['name'] === 'string' ? (proxy['name'] as string) : ''
|
||||||
})
|
)
|
||||||
|
.filter(Boolean) as string[])
|
||||||
// 插入后置规则
|
|
||||||
const { updatedRules, ruleIndices } = processAppendRulesWithPositions(
|
|
||||||
appendRules,
|
|
||||||
allRules,
|
|
||||||
(rule, currentRules) => {
|
|
||||||
if (rule.offset !== undefined) {
|
|
||||||
return Math.max(0, currentRules.length - rule.offset)
|
|
||||||
}
|
|
||||||
return currentRules.length
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
allRules = updatedRules
|
|
||||||
|
|
||||||
// 标记后置规则
|
|
||||||
ruleIndices.forEach((index) => newAppendRules.add(index))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理删除规则
|
groups.push('DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE')
|
||||||
if (ruleData.delete && Array.isArray(ruleData.delete)) {
|
setProxyGroups([...new Set(groups)])
|
||||||
const deleteRules = ruleData.delete.map((ruleStr: string) => {
|
}
|
||||||
return parseRuleString(ruleStr)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 匹配并标记删除规则
|
try {
|
||||||
deleteRules.forEach((deleteRule) => {
|
const ruleContent = await getRuleStr(id)
|
||||||
const matchedIndex = allRules.findIndex(
|
const ruleData = yaml.load(ruleContent) as {
|
||||||
(rule) =>
|
prepend?: string[]
|
||||||
rule.type === deleteRule.type &&
|
append?: string[]
|
||||||
rule.payload === deleteRule.payload &&
|
delete?: string[]
|
||||||
rule.proxy === deleteRule.proxy &&
|
}
|
||||||
JSON.stringify(rule.additionalParams || []) ===
|
|
||||||
JSON.stringify(deleteRule.additionalParams || [])
|
if (ruleData) {
|
||||||
|
let allRules = [...initialRules]
|
||||||
|
const newPrependRules = new Set<number>()
|
||||||
|
const newAppendRules = new Set<number>()
|
||||||
|
const newDeletedRules = new Set<number>()
|
||||||
|
|
||||||
|
if (ruleData.prepend && Array.isArray(ruleData.prepend)) {
|
||||||
|
const prependRuleItems: RuleItem[] = []
|
||||||
|
ruleData.prepend.forEach((ruleStr: string) => {
|
||||||
|
prependRuleItems.push(parseRuleString(ruleStr))
|
||||||
|
})
|
||||||
|
|
||||||
|
const { updatedRules, ruleIndices } = processRulesWithPositions(
|
||||||
|
prependRuleItems,
|
||||||
|
allRules,
|
||||||
|
(rule, currentRules) => {
|
||||||
|
if (rule.offset !== undefined && rule.offset < currentRules.length) {
|
||||||
|
return rule.offset
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (matchedIndex !== -1) {
|
allRules = updatedRules
|
||||||
newDeletedRules.add(matchedIndex)
|
ruleIndices.forEach((index) => newPrependRules.add(index))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
if (ruleData.append && Array.isArray(ruleData.append)) {
|
||||||
|
const appendRuleItems: RuleItem[] = []
|
||||||
|
ruleData.append.forEach((ruleStr: string) => {
|
||||||
|
appendRuleItems.push(parseRuleString(ruleStr))
|
||||||
|
})
|
||||||
|
|
||||||
|
const { updatedRules, ruleIndices } = processAppendRulesWithPositions(
|
||||||
|
appendRuleItems,
|
||||||
|
allRules,
|
||||||
|
(rule, currentRules) => {
|
||||||
|
if (rule.offset !== undefined) {
|
||||||
|
return Math.max(0, currentRules.length - rule.offset)
|
||||||
|
}
|
||||||
|
return currentRules.length
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
allRules = updatedRules
|
||||||
|
ruleIndices.forEach((index) => newAppendRules.add(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ruleData.delete && Array.isArray(ruleData.delete)) {
|
||||||
|
const deleteRules = ruleData.delete.map((ruleStr: string) => {
|
||||||
|
return parseRuleString(ruleStr)
|
||||||
|
})
|
||||||
|
|
||||||
|
deleteRules.forEach((deleteRule) => {
|
||||||
|
const matchedIndex = allRules.findIndex(
|
||||||
|
(rule) =>
|
||||||
|
rule.type === deleteRule.type &&
|
||||||
|
rule.payload === deleteRule.payload &&
|
||||||
|
rule.proxy === deleteRule.proxy &&
|
||||||
|
JSON.stringify(rule.additionalParams || []) ===
|
||||||
|
JSON.stringify(deleteRule.additionalParams || [])
|
||||||
|
)
|
||||||
|
|
||||||
|
if (matchedIndex !== -1) {
|
||||||
|
newDeletedRules.add(matchedIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setPrependRules(newPrependRules)
|
||||||
|
setAppendRules(newAppendRules)
|
||||||
|
setDeletedRules(newDeletedRules)
|
||||||
|
setRules(allRules)
|
||||||
|
} else {
|
||||||
|
setRules(initialRules)
|
||||||
|
setPrependRules(new Set())
|
||||||
|
setAppendRules(new Set())
|
||||||
|
setDeletedRules(new Set())
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
// 更新状态
|
|
||||||
setPrependRules(newPrependRules)
|
|
||||||
setAppendRules(newAppendRules)
|
|
||||||
setDeletedRules(newDeletedRules)
|
|
||||||
|
|
||||||
// 设置规则列表
|
|
||||||
setRules(allRules)
|
|
||||||
} else {
|
|
||||||
// 使用初始规则
|
|
||||||
setRules(initialRules)
|
setRules(initialRules)
|
||||||
// 清空规则标记
|
|
||||||
setPrependRules(new Set())
|
setPrependRules(new Set())
|
||||||
setAppendRules(new Set())
|
setAppendRules(new Set())
|
||||||
setDeletedRules(new Set())
|
setDeletedRules(new Set())
|
||||||
}
|
}
|
||||||
} catch (ruleError) {
|
} catch {
|
||||||
// 规则文件读取失败
|
// 解析配置文件失败,静默处理
|
||||||
console.debug('规则文件读取失败:', ruleError)
|
} finally {
|
||||||
setRules(initialRules)
|
setIsLoading(false)
|
||||||
// 清空规则标记
|
|
||||||
setPrependRules(new Set())
|
|
||||||
setAppendRules(new Set())
|
|
||||||
setDeletedRules(new Set())
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to parse profile content', e)
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
loadContent()
|
||||||
|
}, [id, parseRuleString, processRulesWithPositions, processAppendRulesWithPositions])
|
||||||
useEffect(() => {
|
|
||||||
getContent()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const validateRulePayload = useCallback((ruleType: string, payload: string): boolean => {
|
const validateRulePayload = useCallback((ruleType: string, payload: string): boolean => {
|
||||||
if (ruleType === 'MATCH') {
|
if (ruleType === 'MATCH') {
|
||||||
@ -843,6 +921,58 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算插入位置的索引
|
||||||
|
const getUpdatedIndexForInsertion = (index: number, insertPosition: number): number => {
|
||||||
|
if (index >= insertPosition) {
|
||||||
|
return index + 1
|
||||||
|
} else {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入规则后更新所有索引
|
||||||
|
const updateAllRuleIndicesAfterInsertion = useCallback(
|
||||||
|
(
|
||||||
|
currentPrependRules: Set<number>,
|
||||||
|
currentAppendRules: Set<number>,
|
||||||
|
currentDeletedRules: Set<number>,
|
||||||
|
insertPosition: number,
|
||||||
|
isNewPrependRule: boolean = false,
|
||||||
|
isNewAppendRule: boolean = false
|
||||||
|
): {
|
||||||
|
newPrependRules: Set<number>
|
||||||
|
newAppendRules: Set<number>
|
||||||
|
newDeletedRules: Set<number>
|
||||||
|
} => {
|
||||||
|
const newPrependRules = new Set<number>()
|
||||||
|
const newAppendRules = new Set<number>()
|
||||||
|
const newDeletedRules = new Set<number>()
|
||||||
|
|
||||||
|
currentPrependRules.forEach((idx) => {
|
||||||
|
newPrependRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||||
|
})
|
||||||
|
|
||||||
|
currentAppendRules.forEach((idx) => {
|
||||||
|
newAppendRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||||
|
})
|
||||||
|
|
||||||
|
currentDeletedRules.forEach((idx) => {
|
||||||
|
newDeletedRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isNewPrependRule) {
|
||||||
|
newPrependRules.add(insertPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNewAppendRule) {
|
||||||
|
newAppendRules.add(insertPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { newPrependRules, newAppendRules, newDeletedRules }
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
const handleAddRule = useCallback(
|
const handleAddRule = useCallback(
|
||||||
(position: 'prepend' | 'append' = 'append'): void => {
|
(position: 'prepend' | 'append' = 'append'): void => {
|
||||||
if (!(newRule.type === 'MATCH' || newRule.payload.trim() !== '')) {
|
if (!(newRule.type === 'MATCH' || newRule.payload.trim() !== '')) {
|
||||||
@ -917,7 +1047,16 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
|||||||
})
|
})
|
||||||
setNewRule({ type: 'DOMAIN', payload: '', proxy: 'DIRECT', additionalParams: [] })
|
setNewRule({ type: 'DOMAIN', payload: '', proxy: 'DIRECT', additionalParams: [] })
|
||||||
},
|
},
|
||||||
[newRule, rules, prependRules, appendRules, deletedRules, validateRulePayload, t]
|
[
|
||||||
|
newRule,
|
||||||
|
rules,
|
||||||
|
prependRules,
|
||||||
|
appendRules,
|
||||||
|
deletedRules,
|
||||||
|
validateRulePayload,
|
||||||
|
t,
|
||||||
|
updateAllRuleIndicesAfterInsertion
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleRemoveRule = useCallback((index: number): void => {
|
const handleRemoveRule = useCallback((index: number): void => {
|
||||||
@ -1002,126 +1141,6 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
|||||||
[rules, prependRules, appendRules]
|
[rules, prependRules, appendRules]
|
||||||
)
|
)
|
||||||
|
|
||||||
// 解析规则字符串
|
|
||||||
const parseRuleString = (ruleStr: string): RuleItem => {
|
|
||||||
const parts = ruleStr.split(',')
|
|
||||||
const firstPartIsNumber =
|
|
||||||
!isNaN(Number(parts[0])) && parts[0].trim() !== '' && parts.length >= 3
|
|
||||||
|
|
||||||
let offset = 0
|
|
||||||
let ruleParts = parts
|
|
||||||
|
|
||||||
if (firstPartIsNumber) {
|
|
||||||
offset = parseInt(parts[0])
|
|
||||||
ruleParts = parts.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ruleParts[0] === 'MATCH') {
|
|
||||||
return {
|
|
||||||
type: 'MATCH',
|
|
||||||
payload: '',
|
|
||||||
proxy: ruleParts[1],
|
|
||||||
offset: offset > 0 ? offset : undefined
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const additionalParams = ruleParts.slice(3).filter((param) => param.trim() !== '') || []
|
|
||||||
return {
|
|
||||||
type: ruleParts[0],
|
|
||||||
payload: ruleParts[1],
|
|
||||||
proxy: ruleParts[2],
|
|
||||||
additionalParams,
|
|
||||||
offset: offset > 0 ? offset : undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 规则转字符串
|
|
||||||
const convertRuleToString = (rule: RuleItem): string => {
|
|
||||||
const parts = [rule.type]
|
|
||||||
if (rule.payload) parts.push(rule.payload)
|
|
||||||
if (rule.proxy) parts.push(rule.proxy)
|
|
||||||
if (rule.additionalParams && rule.additionalParams.length > 0) {
|
|
||||||
parts.push(...rule.additionalParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加偏移量
|
|
||||||
if (rule.offset !== undefined && rule.offset > 0) {
|
|
||||||
parts.unshift(rule.offset.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.join(',')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理前置规则位置
|
|
||||||
const processRulesWithPositions = (
|
|
||||||
rules: RuleItem[],
|
|
||||||
allRules: RuleItem[],
|
|
||||||
positionCalculator: (rule: RuleItem, currentRules: RuleItem[]) => number
|
|
||||||
): { updatedRules: RuleItem[]; ruleIndices: Set<number> } => {
|
|
||||||
const updatedRules = [...allRules]
|
|
||||||
const ruleIndices = new Set<number>()
|
|
||||||
|
|
||||||
// 按顺序处理规则
|
|
||||||
rules.forEach((rule) => {
|
|
||||||
const targetPosition = positionCalculator(rule, updatedRules)
|
|
||||||
const actualPosition = Math.min(targetPosition, updatedRules.length)
|
|
||||||
updatedRules.splice(actualPosition, 0, rule)
|
|
||||||
|
|
||||||
// 更新索引
|
|
||||||
const newRuleIndices = new Set<number>()
|
|
||||||
ruleIndices.forEach((idx) => {
|
|
||||||
if (idx >= actualPosition) {
|
|
||||||
newRuleIndices.add(idx + 1)
|
|
||||||
} else {
|
|
||||||
newRuleIndices.add(idx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// 添加当前规则索引
|
|
||||||
newRuleIndices.add(actualPosition)
|
|
||||||
|
|
||||||
// 更新索引集合
|
|
||||||
ruleIndices.clear()
|
|
||||||
newRuleIndices.forEach((idx) => ruleIndices.add(idx))
|
|
||||||
})
|
|
||||||
|
|
||||||
return { updatedRules, ruleIndices }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理后置规则位置
|
|
||||||
const processAppendRulesWithPositions = (
|
|
||||||
rules: RuleItem[],
|
|
||||||
allRules: RuleItem[],
|
|
||||||
positionCalculator: (rule: RuleItem, currentRules: RuleItem[]) => number
|
|
||||||
): { updatedRules: RuleItem[]; ruleIndices: Set<number> } => {
|
|
||||||
const updatedRules = [...allRules]
|
|
||||||
const ruleIndices = new Set<number>()
|
|
||||||
|
|
||||||
// 按顺序处理规则
|
|
||||||
rules.forEach((rule) => {
|
|
||||||
const targetPosition = positionCalculator(rule, updatedRules)
|
|
||||||
const actualPosition = Math.min(targetPosition, updatedRules.length)
|
|
||||||
updatedRules.splice(actualPosition, 0, rule)
|
|
||||||
|
|
||||||
// 更新索引
|
|
||||||
const newRuleIndices = new Set<number>()
|
|
||||||
ruleIndices.forEach((idx) => {
|
|
||||||
if (idx >= actualPosition) {
|
|
||||||
newRuleIndices.add(idx + 1)
|
|
||||||
} else {
|
|
||||||
newRuleIndices.add(idx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// 添加当前规则索引
|
|
||||||
newRuleIndices.add(actualPosition)
|
|
||||||
|
|
||||||
// 更新索引集合
|
|
||||||
ruleIndices.clear()
|
|
||||||
newRuleIndices.forEach((idx) => ruleIndices.add(idx))
|
|
||||||
})
|
|
||||||
|
|
||||||
return { updatedRules, ruleIndices }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新规则索引
|
// 更新规则索引
|
||||||
const updateRuleIndices = (prev: Set<number>, index1: number, index2: number): Set<number> => {
|
const updateRuleIndices = (prev: Set<number>, index1: number, index2: number): Set<number> => {
|
||||||
const newSet = new Set<number>()
|
const newSet = new Set<number>()
|
||||||
@ -1137,57 +1156,20 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
|||||||
return newSet
|
return newSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算插入位置的索引
|
// 规则转字符串
|
||||||
const getUpdatedIndexForInsertion = (index: number, insertPosition: number): number => {
|
const convertRuleToString = (rule: RuleItem): string => {
|
||||||
if (index >= insertPosition) {
|
const parts = [rule.type]
|
||||||
return index + 1
|
if (rule.payload) parts.push(rule.payload)
|
||||||
} else {
|
if (rule.proxy) parts.push(rule.proxy)
|
||||||
return index
|
if (rule.additionalParams && rule.additionalParams.length > 0) {
|
||||||
}
|
parts.push(...rule.additionalParams)
|
||||||
}
|
|
||||||
|
|
||||||
// 插入规则后更新所有索引
|
|
||||||
const updateAllRuleIndicesAfterInsertion = (
|
|
||||||
prependRules: Set<number>,
|
|
||||||
appendRules: Set<number>,
|
|
||||||
deletedRules: Set<number>,
|
|
||||||
insertPosition: number,
|
|
||||||
isNewPrependRule: boolean = false,
|
|
||||||
isNewAppendRule: boolean = false
|
|
||||||
): {
|
|
||||||
newPrependRules: Set<number>
|
|
||||||
newAppendRules: Set<number>
|
|
||||||
newDeletedRules: Set<number>
|
|
||||||
} => {
|
|
||||||
const newPrependRules = new Set<number>()
|
|
||||||
const newAppendRules = new Set<number>()
|
|
||||||
const newDeletedRules = new Set<number>()
|
|
||||||
|
|
||||||
// 更新前置规则索引
|
|
||||||
prependRules.forEach((idx) => {
|
|
||||||
newPrependRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更新后置规则索引
|
|
||||||
appendRules.forEach((idx) => {
|
|
||||||
newAppendRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更新删除规则索引
|
|
||||||
deletedRules.forEach((idx) => {
|
|
||||||
newDeletedRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 标记新规则
|
|
||||||
if (isNewPrependRule) {
|
|
||||||
newPrependRules.add(insertPosition)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNewAppendRule) {
|
if (rule.offset !== undefined && rule.offset > 0) {
|
||||||
newAppendRules.add(insertPosition)
|
parts.unshift(rule.offset.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
return { newPrependRules, newAppendRules, newDeletedRules }
|
return parts.join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -15,14 +15,14 @@ import { calcPercent, calcTraffic } from '@renderer/utils/calc'
|
|||||||
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
||||||
import dayjs from '@renderer/utils/dayjs'
|
import dayjs from '@renderer/utils/dayjs'
|
||||||
import React, { Key, useMemo, useState } from 'react'
|
import React, { Key, useMemo, useState } from 'react'
|
||||||
import EditFileModal from './edit-file-modal'
|
|
||||||
import EditInfoModal from './edit-info-modal'
|
|
||||||
import EditRulesModal from './edit-rules-modal'
|
|
||||||
import { useSortable } from '@dnd-kit/sortable'
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import { openFile } from '@renderer/utils/ipc'
|
import { openFile } from '@renderer/utils/ipc'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import EditRulesModal from './edit-rules-modal'
|
||||||
|
import EditInfoModal from './edit-info-modal'
|
||||||
|
import EditFileModal from './edit-file-modal'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
info: IProfileItem
|
info: IProfileItem
|
||||||
|
|||||||
@ -4,10 +4,7 @@ import {
|
|||||||
getRuntimeConfig
|
getRuntimeConfig
|
||||||
} from '@renderer/utils/ipc'
|
} from '@renderer/utils/ipc'
|
||||||
import { Fragment, useEffect, useMemo, useState } from 'react'
|
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||||
import Viewer from './viewer'
|
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import SettingCard from '../base/base-setting-card'
|
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { Button, Chip } from '@heroui/react'
|
import { Button, Chip } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
@ -17,6 +14,9 @@ import dayjs from '@renderer/utils/dayjs'
|
|||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
import { getHash } from '@renderer/utils/hash'
|
import { getHash } from '@renderer/utils/hash'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import SettingCard from '../base/base-setting-card'
|
||||||
|
import Viewer from './viewer'
|
||||||
const ProxyProvider: React.FC = () => {
|
const ProxyProvider: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [showDetails, setShowDetails] = useState({
|
const [showDetails, setShowDetails] = useState({
|
||||||
@ -126,7 +126,7 @@ const ProxyProvider: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
title={
|
title={
|
||||||
provider.vehicleType == 'File' ? t('common.editor.edit') : t('common.viewer.view')
|
provider.vehicleType === 'File' ? t('common.editor.edit') : t('common.viewer.view')
|
||||||
}
|
}
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -140,7 +140,7 @@ const ProxyProvider: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{provider.vehicleType == 'File' ? (
|
{provider.vehicleType === 'File' ? (
|
||||||
<MdEditDocument className={`text-lg`} />
|
<MdEditDocument className={`text-lg`} />
|
||||||
) : (
|
) : (
|
||||||
<CgLoadbarDoc className={`text-lg`} />
|
<CgLoadbarDoc className={`text-lg`} />
|
||||||
|
|||||||
@ -4,11 +4,8 @@ import {
|
|||||||
getRuntimeConfig
|
getRuntimeConfig
|
||||||
} from '@renderer/utils/ipc'
|
} from '@renderer/utils/ipc'
|
||||||
import { getHash } from '@renderer/utils/hash'
|
import { getHash } from '@renderer/utils/hash'
|
||||||
import Viewer from './viewer'
|
|
||||||
import { Fragment, useEffect, useMemo, useState } from 'react'
|
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import SettingCard from '../base/base-setting-card'
|
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { Button, Chip } from '@heroui/react'
|
import { Button, Chip } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
@ -16,6 +13,9 @@ import { CgLoadbarDoc } from 'react-icons/cg'
|
|||||||
import { MdEditDocument } from 'react-icons/md'
|
import { MdEditDocument } from 'react-icons/md'
|
||||||
import dayjs from '@renderer/utils/dayjs'
|
import dayjs from '@renderer/utils/dayjs'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import SettingCard from '../base/base-setting-card'
|
||||||
|
import Viewer from './viewer'
|
||||||
|
|
||||||
const RuleProvider: React.FC = () => {
|
const RuleProvider: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -138,7 +138,7 @@ const RuleProvider: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
title={
|
title={
|
||||||
provider.vehicleType == 'File' ? t('common.editor.edit') : t('common.viewer.view')
|
provider.vehicleType === 'File' ? t('common.editor.edit') : t('common.viewer.view')
|
||||||
}
|
}
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -154,7 +154,7 @@ const RuleProvider: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{provider.vehicleType == 'File' ? (
|
{provider.vehicleType === 'File' ? (
|
||||||
<MdEditDocument className={`text-lg`} />
|
<MdEditDocument className={`text-lg`} />
|
||||||
) : (
|
) : (
|
||||||
<CgLoadbarDoc className={`text-lg`} />
|
<CgLoadbarDoc className={`text-lg`} />
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { BaseEditor } from '../base/base-editor'
|
|
||||||
import { getFileStr, setFileStr, convertMrsRuleset, getRuntimeConfig } from '@renderer/utils/ipc'
|
import { getFileStr, setFileStr, convertMrsRuleset, getRuntimeConfig } from '@renderer/utils/ipc'
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { BaseEditor } from '../base/base-editor'
|
||||||
type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
|
type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -20,63 +20,66 @@ const Viewer: React.FC<Props> = (props) => {
|
|||||||
const { type, path, title, format, privderType, behavior, onClose } = props
|
const { type, path, title, format, privderType, behavior, onClose } = props
|
||||||
const [currData, setCurrData] = useState('')
|
const [currData, setCurrData] = useState('')
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
let language: Language = !format || format === 'YamlRule' ? 'yaml' : 'text'
|
|
||||||
|
|
||||||
const getContent = async (): Promise<void> => {
|
const language: Language = useMemo(() => {
|
||||||
setIsLoading(true)
|
if (format === 'MrsRule') return 'text'
|
||||||
try {
|
if (type === 'Inline') return 'yaml'
|
||||||
let fileContent: React.SetStateAction<string>
|
if (!format || format === 'YamlRule') return 'yaml'
|
||||||
|
return 'text'
|
||||||
if (format === 'MrsRule') {
|
}, [format, type])
|
||||||
language = 'text'
|
|
||||||
let ruleBehavior: string = behavior || 'domain'
|
|
||||||
if (!behavior) {
|
|
||||||
try {
|
|
||||||
const runtimeConfig = await getRuntimeConfig()
|
|
||||||
const provider = runtimeConfig['rule-providers']?.[title]
|
|
||||||
ruleBehavior = provider?.behavior || 'domain'
|
|
||||||
} catch {
|
|
||||||
ruleBehavior = 'domain'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileContent = await convertMrsRuleset(path, ruleBehavior)
|
|
||||||
setCurrData(fileContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'Inline') {
|
|
||||||
fileContent = await getFileStr('config.yaml')
|
|
||||||
language = 'yaml'
|
|
||||||
} else {
|
|
||||||
fileContent = await getFileStr(path)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const parsedYaml = yaml.load(fileContent)
|
|
||||||
if (privderType === 'proxy-providers') {
|
|
||||||
setCurrData(
|
|
||||||
yaml.dump({
|
|
||||||
proxies: parsedYaml[privderType][title].payload
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
setCurrData(
|
|
||||||
yaml.dump({
|
|
||||||
rules: parsedYaml[privderType][title].payload
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setCurrData(fileContent)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getContent()
|
const loadContent = async (): Promise<void> => {
|
||||||
}, [])
|
setIsLoading(true)
|
||||||
|
try {
|
||||||
|
let fileContent: React.SetStateAction<string>
|
||||||
|
|
||||||
|
if (format === 'MrsRule') {
|
||||||
|
let ruleBehavior: string = behavior || 'domain'
|
||||||
|
if (!behavior) {
|
||||||
|
try {
|
||||||
|
const runtimeConfig = await getRuntimeConfig()
|
||||||
|
const provider = runtimeConfig['rule-providers']?.[title]
|
||||||
|
ruleBehavior = provider?.behavior || 'domain'
|
||||||
|
} catch {
|
||||||
|
ruleBehavior = 'domain'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileContent = await convertMrsRuleset(path, ruleBehavior)
|
||||||
|
setCurrData(fileContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'Inline') {
|
||||||
|
fileContent = await getFileStr('config.yaml')
|
||||||
|
} else {
|
||||||
|
fileContent = await getFileStr(path)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsedYaml = yaml.load(fileContent)
|
||||||
|
if (privderType === 'proxy-providers') {
|
||||||
|
setCurrData(
|
||||||
|
yaml.dump({
|
||||||
|
proxies: parsedYaml[privderType][title].payload
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setCurrData(
|
||||||
|
yaml.dump({
|
||||||
|
rules: parsedYaml[privderType][title].payload
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setCurrData(fileContent)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadContent()
|
||||||
|
}, [path, type, title, format, privderType, behavior])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -99,7 +102,7 @@ const Viewer: React.FC<Props> = (props) => {
|
|||||||
<BaseEditor
|
<BaseEditor
|
||||||
language={language}
|
language={language}
|
||||||
value={currData}
|
value={currData}
|
||||||
readOnly={type != 'File' || format === 'MrsRule'}
|
readOnly={type !== 'File' || format === 'MrsRule'}
|
||||||
onChange={(value) => setCurrData(value)}
|
onChange={(value) => setCurrData(value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -108,7 +111,7 @@ const Viewer: React.FC<Props> = (props) => {
|
|||||||
<Button size="sm" variant="light" onPress={onClose}>
|
<Button size="sm" variant="light" onPress={onClose}>
|
||||||
{t('common.close')}
|
{t('common.close')}
|
||||||
</Button>
|
</Button>
|
||||||
{type == 'File' && format !== 'MrsRule' && (
|
{type === 'File' && format !== 'MrsRule' && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { Button, Tooltip } from '@heroui/react'
|
import { Button, Tooltip } from '@heroui/react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import {
|
import {
|
||||||
checkUpdate,
|
checkUpdate,
|
||||||
createHeapSnapshot,
|
createHeapSnapshot,
|
||||||
@ -10,11 +8,13 @@ import {
|
|||||||
resetAppConfig
|
resetAppConfig
|
||||||
} from '@renderer/utils/ipc'
|
} from '@renderer/utils/ipc'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import UpdaterModal from '../updater/updater-modal'
|
|
||||||
import { version } from '@renderer/utils/init'
|
import { version } from '@renderer/utils/init'
|
||||||
import { IoIosHelpCircle } from 'react-icons/io'
|
import { IoIosHelpCircle } from 'react-icons/io'
|
||||||
import { getDriver } from '@renderer/App'
|
import { getDriver } from '@renderer/App'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import UpdaterModal from '../updater/updater-modal'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import SettingCard from '../base/base-setting-card'
|
||||||
import BaseConfirmModal from '../base/base-confirm-modal'
|
import BaseConfirmModal from '../base/base-confirm-modal'
|
||||||
|
|
||||||
const Actions: React.FC = () => {
|
const Actions: React.FC = () => {
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { Button, Input, Select, SelectItem, Switch, Tab, Tabs, Tooltip } from '@heroui/react'
|
import { Button, Input, Select, SelectItem, Switch, Tab, Tabs, Tooltip } from '@heroui/react'
|
||||||
import { BiCopy, BiSolidFileImport } from 'react-icons/bi'
|
import { BiCopy, BiSolidFileImport } from 'react-icons/bi'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
@ -30,9 +28,11 @@ import { platform } from '@renderer/utils/init'
|
|||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { IoIosHelpCircle, IoMdCloudDownload } from 'react-icons/io'
|
import { IoIosHelpCircle, IoMdCloudDownload } from 'react-icons/io'
|
||||||
import { MdEditDocument } from 'react-icons/md'
|
import { MdEditDocument } from 'react-icons/md'
|
||||||
import CSSEditorModal from './css-editor-modal'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import SettingCard from '../base/base-setting-card'
|
||||||
import BaseConfirmModal from '../base/base-confirm-modal'
|
import BaseConfirmModal from '../base/base-confirm-modal'
|
||||||
|
import CSSEditorModal from './css-editor-modal'
|
||||||
|
|
||||||
const GeneralConfig: React.FC = () => {
|
const GeneralConfig: React.FC = () => {
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { Button, useDisclosure } from '@heroui/react'
|
import { Button, useDisclosure } from '@heroui/react'
|
||||||
import { exportLocalBackup, importLocalBackup } from '@renderer/utils/ipc'
|
import { exportLocalBackup, importLocalBackup } from '@renderer/utils/ipc'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import SettingCard from '../base/base-setting-card'
|
||||||
import BaseConfirmModal from '../base/base-confirm-modal'
|
import BaseConfirmModal from '../base/base-confirm-modal'
|
||||||
|
|
||||||
const LocalBackupConfig: React.FC = () => {
|
const LocalBackupConfig: React.FC = () => {
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
|
import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import debounce from '@renderer/utils/debounce'
|
import debounce from '@renderer/utils/debounce'
|
||||||
@ -11,6 +9,8 @@ import { BiCopy } from 'react-icons/bi'
|
|||||||
import { IoIosHelpCircle } from 'react-icons/io'
|
import { IoIosHelpCircle } from 'react-icons/io'
|
||||||
import { platform, version } from '@renderer/utils/init'
|
import { platform, version } from '@renderer/utils/init'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import SettingCard from '../base/base-setting-card'
|
||||||
|
|
||||||
const MihomoConfig: React.FC = () => {
|
const MihomoConfig: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { Button, Input } from '@heroui/react'
|
import { Button, Input } from '@heroui/react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import React, { KeyboardEvent, useState } from 'react'
|
import React, { KeyboardEvent, useState } from 'react'
|
||||||
import { platform } from '@renderer/utils/init'
|
import { platform } from '@renderer/utils/init'
|
||||||
import { registerShortcut } from '@renderer/utils/ipc'
|
import { registerShortcut } from '@renderer/utils/ipc'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import SettingCard from '../base/base-setting-card'
|
||||||
|
|
||||||
const keyMap = {
|
const keyMap = {
|
||||||
Backquote: '`',
|
Backquote: '`',
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import SettingItem from '../base/base-setting-item'
|
|
||||||
import { Button, Input, Select, SelectItem, Switch } from '@heroui/react'
|
import { Button, Input, Select, SelectItem, Switch } from '@heroui/react'
|
||||||
import { listWebdavBackups, webdavBackup, reinitWebdavBackupScheduler } from '@renderer/utils/ipc'
|
import { listWebdavBackups, webdavBackup, reinitWebdavBackupScheduler } from '@renderer/utils/ipc'
|
||||||
import WebdavRestoreModal from './webdav-restore-modal'
|
|
||||||
import debounce from '@renderer/utils/debounce'
|
import debounce from '@renderer/utils/debounce'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { isValidCron } from 'cron-validator'
|
import { isValidCron } from 'cron-validator'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import SettingCard from '../base/base-setting-card'
|
||||||
|
import WebdavRestoreModal from './webdav-restore-modal'
|
||||||
|
|
||||||
const WebdavConfig: React.FC = () => {
|
const WebdavConfig: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { BaseEditor } from '../base/base-editor'
|
|
||||||
import { getRuntimeConfigStr } from '@renderer/utils/ipc'
|
import { getRuntimeConfigStr } from '@renderer/utils/ipc'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { BaseEditor } from '../base/base-editor'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import React, { useEffect, useState, useMemo, useRef, useCallback } from 'react'
|
|||||||
import { useSortable } from '@dnd-kit/sortable'
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import { IoLink } from 'react-icons/io5'
|
import { IoLink } from 'react-icons/io5'
|
||||||
|
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import { platform } from '@renderer/utils/init'
|
import { platform } from '@renderer/utils/init'
|
||||||
import { Line } from 'react-chartjs-2'
|
import { Line } from 'react-chartjs-2'
|
||||||
|
|||||||
@ -51,7 +51,7 @@ const MihomoCoreCard: React.FC<Props> = (props) => {
|
|||||||
PubSub.unsubscribe(token)
|
PubSub.unsubscribe(token)
|
||||||
window.electron.ipcRenderer.removeAllListeners('mihomoMemory')
|
window.electron.ipcRenderer.removeAllListeners('mihomoMemory')
|
||||||
}
|
}
|
||||||
}, [])
|
}, [mutate])
|
||||||
|
|
||||||
if (iconOnly) {
|
if (iconOnly) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -10,10 +10,10 @@ import { CSS } from '@dnd-kit/utilities'
|
|||||||
import 'dayjs/locale/zh-cn'
|
import 'dayjs/locale/zh-cn'
|
||||||
import dayjs from '@renderer/utils/dayjs'
|
import dayjs from '@renderer/utils/dayjs'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import ConfigViewer from './config-viewer'
|
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import { TiFolder } from 'react-icons/ti'
|
import { TiFolder } from 'react-icons/ti'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import ConfigViewer from './config-viewer'
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
dayjs.locale('zh-cn')
|
dayjs.locale('zh-cn')
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
|||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { useSortable } from '@dnd-kit/sortable'
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import SubStoreIcon from '../base/substore-icon'
|
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SubStoreIcon from '../base/substore-icon'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
iconOnly?: boolean
|
iconOnly?: boolean
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { useAppConfig } from '@renderer/hooks/use-app-config'
|
|||||||
import { checkUpdate } from '@renderer/utils/ipc'
|
import { checkUpdate } from '@renderer/utils/ipc'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import UpdaterModal from './updater-modal'
|
|
||||||
import { platform } from '@renderer/utils/init'
|
import { platform } from '@renderer/utils/init'
|
||||||
import { MdNewReleases } from 'react-icons/md'
|
import { MdNewReleases } from 'react-icons/md'
|
||||||
|
import UpdaterModal from './updater-modal'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
iconOnly?: boolean
|
iconOnly?: boolean
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React, { ReactNode, useCallback } from 'react'
|
import React, { ReactNode, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { showError } from '@renderer/utils/error-display'
|
import { showError } from '@renderer/utils/error-display'
|
||||||
import { createConfigContext } from './create-config-context'
|
|
||||||
import { getAppConfig, patchAppConfig as patch } from '@renderer/utils/ipc'
|
import { getAppConfig, patchAppConfig as patch } from '@renderer/utils/ipc'
|
||||||
|
import { createConfigContext } from './create-config-context'
|
||||||
|
|
||||||
const { Provider, useConfig } = createConfigContext<IAppConfig>({
|
const { Provider, useConfig } = createConfigContext<IAppConfig>({
|
||||||
swrKey: 'getAppConfig',
|
swrKey: 'getAppConfig',
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export const ControledMihomoConfigProvider: React.FC<{ children: ReactNode }> =
|
|||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeListener('controledMihomoConfigUpdated', handler)
|
window.electron.ipcRenderer.removeListener('controledMihomoConfigUpdated', handler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [mutateControledMihomoConfig])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ControledMihomoConfigContext.Provider
|
<ControledMihomoConfigContext.Provider
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export const GroupsProvider: React.FC<{ children: ReactNode }> = ({ children })
|
|||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeListener('groupsUpdated', handler)
|
window.electron.ipcRenderer.removeListener('groupsUpdated', handler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [mutate])
|
||||||
|
|
||||||
return <GroupsContext.Provider value={{ groups, mutate }}>{children}</GroupsContext.Provider>
|
return <GroupsContext.Provider value={{ groups, mutate }}>{children}</GroupsContext.Provider>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React, { ReactNode, useCallback } from 'react'
|
import React, { ReactNode, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { showError } from '@renderer/utils/error-display'
|
import { showError } from '@renderer/utils/error-display'
|
||||||
import { createConfigContext } from './create-config-context'
|
|
||||||
import {
|
import {
|
||||||
getOverrideConfig,
|
getOverrideConfig,
|
||||||
setOverrideConfig as set,
|
setOverrideConfig as set,
|
||||||
@ -9,6 +8,7 @@ import {
|
|||||||
removeOverrideItem as remove,
|
removeOverrideItem as remove,
|
||||||
updateOverrideItem as update
|
updateOverrideItem as update
|
||||||
} from '@renderer/utils/ipc'
|
} from '@renderer/utils/ipc'
|
||||||
|
import { createConfigContext } from './create-config-context'
|
||||||
|
|
||||||
const { Provider, useConfig } = createConfigContext<IOverrideConfig>({
|
const { Provider, useConfig } = createConfigContext<IOverrideConfig>({
|
||||||
swrKey: 'getOverrideConfig',
|
swrKey: 'getOverrideConfig',
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React, { ReactNode, useCallback, useRef } from 'react'
|
import React, { ReactNode, useCallback, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { showError } from '@renderer/utils/error-display'
|
import { showError } from '@renderer/utils/error-display'
|
||||||
import { createConfigContext } from './create-config-context'
|
|
||||||
import {
|
import {
|
||||||
addProfileItem as add,
|
addProfileItem as add,
|
||||||
changeCurrentProfile as change,
|
changeCurrentProfile as change,
|
||||||
@ -10,6 +9,7 @@ import {
|
|||||||
setProfileConfig as set,
|
setProfileConfig as set,
|
||||||
updateProfileItem as update
|
updateProfileItem as update
|
||||||
} from '@renderer/utils/ipc'
|
} from '@renderer/utils/ipc'
|
||||||
|
import { createConfigContext } from './create-config-context'
|
||||||
|
|
||||||
const { Provider, useConfig } = createConfigContext<IProfileConfig>({
|
const { Provider, useConfig } = createConfigContext<IProfileConfig>({
|
||||||
swrKey: 'getProfileConfig',
|
swrKey: 'getProfileConfig',
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export const RulesProvider: React.FC<{ children: ReactNode }> = ({ children }) =
|
|||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeListener('rulesUpdated', handler)
|
window.electron.ipcRenderer.removeListener('rulesUpdated', handler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [mutate])
|
||||||
|
|
||||||
return <RulesContext.Provider value={{ rules, mutate }}>{children}</RulesContext.Provider>
|
return <RulesContext.Provider value={{ rules, mutate }}>{children}</RulesContext.Provider>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { mihomoCloseAllConnections, mihomoCloseConnection } from '@renderer/utils/ipc'
|
import { mihomoCloseAllConnections, mihomoCloseConnection } from '@renderer/utils/ipc'
|
||||||
import { Key, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { Key, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Badge, Button, Divider, Input, Select, SelectItem, Tab, Tabs } from '@heroui/react'
|
import { Badge, Button, Divider, Input, Select, SelectItem, Tab, Tabs , Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@heroui/react'
|
||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
import ConnectionItem from '@renderer/components/connections/connection-item'
|
import ConnectionItem from '@renderer/components/connections/connection-item'
|
||||||
import ConnectionTable from '@renderer/components/connections/connection-table'
|
import ConnectionTable from '@renderer/components/connections/connection-table'
|
||||||
@ -13,7 +13,6 @@ import { useAppConfig } from '@renderer/hooks/use-app-config'
|
|||||||
import { HiSortAscending, HiSortDescending } from 'react-icons/hi'
|
import { HiSortAscending, HiSortDescending } from 'react-icons/hi'
|
||||||
import { MdViewList, MdTableChart } from 'react-icons/md'
|
import { MdViewList, MdTableChart } from 'react-icons/md'
|
||||||
import { HiOutlineAdjustmentsHorizontal } from 'react-icons/hi2'
|
import { HiOutlineAdjustmentsHorizontal } from 'react-icons/hi2'
|
||||||
import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@heroui/react'
|
|
||||||
import { includesIgnoreCase } from '@renderer/utils/includes'
|
import { includesIgnoreCase } from '@renderer/utils/includes'
|
||||||
import differenceWith from 'lodash/differenceWith'
|
import differenceWith from 'lodash/differenceWith'
|
||||||
import unionWith from 'lodash/unionWith'
|
import unionWith from 'lodash/unionWith'
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'
|
|||||||
import { IoLocationSharp } from 'react-icons/io5'
|
import { IoLocationSharp } from 'react-icons/io5'
|
||||||
import { CgTrash } from 'react-icons/cg'
|
import { CgTrash } from 'react-icons/cg'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { includesIgnoreCase } from '@renderer/utils/includes'
|
import { includesIgnoreCase } from '@renderer/utils/includes'
|
||||||
|
|
||||||
const LOGS_FILTER_KEY = 'logs-filter'
|
const LOGS_FILTER_KEY = 'logs-filter'
|
||||||
|
|||||||
@ -52,6 +52,34 @@ const CoreMap = {
|
|||||||
'mihomo-specific': 'mihomo.specificVersion'
|
'mihomo-specific': 'mihomo.specificVersion'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WebUIPanel {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
isDefault?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultWebUIPanels: WebUIPanel[] = [
|
||||||
|
{
|
||||||
|
id: 'metacubexd',
|
||||||
|
name: 'MetaCubeXD',
|
||||||
|
url: 'https://metacubex.github.io/metacubexd/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'yacd',
|
||||||
|
name: 'YACD',
|
||||||
|
url: 'https://yacd.metacubex.one/?hostname=%host&port=%port&secret=%secret',
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zashboard',
|
||||||
|
name: 'Zashboard',
|
||||||
|
url: 'https://board.zash.run.place/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
|
||||||
|
isDefault: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
const Mihomo: React.FC = () => {
|
const Mihomo: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { appConfig, patchAppConfig } = useAppConfig()
|
const { appConfig, patchAppConfig } = useAppConfig()
|
||||||
@ -79,13 +107,6 @@ const Mihomo: React.FC = () => {
|
|||||||
} = appConfig || {}
|
} = appConfig || {}
|
||||||
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
||||||
|
|
||||||
interface WebUIPanel {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
isDefault?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ipv6,
|
ipv6,
|
||||||
'external-controller': externalController = '',
|
'external-controller': externalController = '',
|
||||||
@ -153,28 +174,6 @@ const Mihomo: React.FC = () => {
|
|||||||
// 生成随机端口 (范围 1024-65535)
|
// 生成随机端口 (范围 1024-65535)
|
||||||
const generateRandomPort = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024
|
const generateRandomPort = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024
|
||||||
|
|
||||||
// 默认 WebUI 面板选项
|
|
||||||
const defaultWebUIPanels: WebUIPanel[] = [
|
|
||||||
{
|
|
||||||
id: 'metacubexd',
|
|
||||||
name: 'MetaCubeXD',
|
|
||||||
url: 'https://metacubex.github.io/metacubexd/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
|
|
||||||
isDefault: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'yacd',
|
|
||||||
name: 'YACD',
|
|
||||||
url: 'https://yacd.metacubex.one/?hostname=%host&port=%port&secret=%secret',
|
|
||||||
isDefault: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'zashboard',
|
|
||||||
name: 'Zashboard',
|
|
||||||
url: 'https://board.zash.run.place/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
|
|
||||||
isDefault: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 初始化面板列表
|
// 初始化面板列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedPanels = localStorage.getItem('webui-panels')
|
const savedPanels = localStorage.getItem('webui-panels')
|
||||||
|
|||||||
@ -126,7 +126,7 @@ const Profiles: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}, [subs, collections])
|
}, [subs, collections, t])
|
||||||
const handleImport = async (): Promise<void> => {
|
const handleImport = async (): Promise<void> => {
|
||||||
setImporting(true)
|
setImporting(true)
|
||||||
await addProfileItem({
|
await addProfileItem({
|
||||||
|
|||||||
@ -85,7 +85,7 @@ const useProxyState = (
|
|||||||
return prev.slice(0, groups.length)
|
return prev.slice(0, groups.length)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [groups.length])
|
}, [groups.length, isOpen.length, setIsOpen])
|
||||||
|
|
||||||
// 保存展开状态
|
// 保存展开状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -133,7 +133,7 @@ const Proxies: React.FC = () => {
|
|||||||
if (groups.length !== searchValue.length) {
|
if (groups.length !== searchValue.length) {
|
||||||
setSearchValue(Array(groups.length).fill(''))
|
setSearchValue(Array(groups.length).fill(''))
|
||||||
}
|
}
|
||||||
}, [groups.length])
|
}, [groups.length, searchValue.length])
|
||||||
|
|
||||||
// 代理列表排序
|
// 代理列表排序
|
||||||
const sortProxies = useCallback((proxies: (IMihomoProxy | IMihomoGroup)[], order: string) => {
|
const sortProxies = useCallback((proxies: (IMihomoProxy | IMihomoGroup)[], order: string) => {
|
||||||
|
|||||||
@ -7,8 +7,7 @@ import PacEditorModal from '@renderer/components/sysproxy/pac-editor-modal'
|
|||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import { platform } from '@renderer/utils/init'
|
import { platform } from '@renderer/utils/init'
|
||||||
import { openUWPTool, triggerSysProxy } from '@renderer/utils/ipc'
|
import { openUWPTool, triggerSysProxy } from '@renderer/utils/ipc'
|
||||||
import { Key, useState } from 'react'
|
import React, { Key, useState } from 'react'
|
||||||
import React from 'react'
|
|
||||||
import { MdDeleteForever } from 'react-icons/md'
|
import { MdDeleteForever } from 'react-icons/md'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export function calcTraffic(byte: number): string {
|
|||||||
function formatNumString(num: number): string {
|
function formatNumString(num: number): string {
|
||||||
let str = num.toFixed(2)
|
let str = num.toFixed(2)
|
||||||
if (str.length <= 5) return str
|
if (str.length <= 5) return str
|
||||||
if (str.length == 6) {
|
if (str.length === 6) {
|
||||||
str = num.toFixed(1)
|
str = num.toFixed(1)
|
||||||
return str
|
return str
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user