block-proxy 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.claude/settings.local.json +26 -1
  2. package/.claude/skills/build-client/skill.md +24 -0
  3. package/.claude/skills/release-client/skill.md +68 -0
  4. package/CLAUDE.md +69 -67
  5. package/Dockerfile +1 -1
  6. package/README.md +38 -24
  7. package/build/asset-manifest.json +6 -6
  8. package/build/index.html +1 -1
  9. package/build/static/css/main.3f317ce6.css +2 -0
  10. package/build/static/css/main.3f317ce6.css.map +1 -0
  11. package/build/static/js/{main.2247fb80.js → main.68f66be0.js} +3 -3
  12. package/build/static/js/main.68f66be0.js.map +1 -0
  13. package/client/app.py +312 -0
  14. package/client/build.sh +84 -0
  15. package/client/config.py +49 -0
  16. package/client/config_window.py +155 -0
  17. package/client/icons/app.icns +0 -0
  18. package/client/icons/app_example.png +0 -0
  19. package/client/icons/app_icon.png +0 -0
  20. package/client/icons/backup/app_example.png +0 -0
  21. package/client/icons/backup/christmas-sock_dark.png +0 -0
  22. package/client/icons/backup/christmas-sock_light.png +0 -0
  23. package/client/icons/backup/socks_on_G.png +0 -0
  24. package/client/icons/backup/socks_on_M.png +0 -0
  25. package/client/icons/christmas-sock_dark.png +0 -0
  26. package/client/icons/christmas-sock_light.png +0 -0
  27. package/client/icons/christmas-sock_light_bar.png +0 -0
  28. package/client/icons/socks_on_G.png +0 -0
  29. package/client/icons/socks_on_G_bar.png +0 -0
  30. package/client/icons/socks_on_M.png +0 -0
  31. package/client/icons/socks_on_M_bar.png +0 -0
  32. package/client/main.py +28 -0
  33. package/client/proxy_core.py +475 -0
  34. package/client/requirements.txt +3 -0
  35. package/client/scripts/download_xray.sh +30 -0
  36. package/client/setup.py +30 -0
  37. package/client/system_proxy.py +94 -0
  38. package/client/tests/__init__.py +0 -0
  39. package/client/tests/test_config.py +72 -0
  40. package/client/tests/test_system_proxy.py +69 -0
  41. package/client/watch-icons.js +31 -0
  42. package/config.json +28 -3
  43. package/docs/superpowers/plans/2026-05-27-blockproxyclient.md +1274 -0
  44. package/docs/superpowers/specs/2026-05-27-blockproxyclient-design.md +264 -0
  45. package/package.json +10 -4
  46. package/proxy/proxy.js +19 -15
  47. package/server/express.js +17 -1
  48. package/src/App.css +596 -276
  49. package/src/App.js +25 -22
  50. package/src/index.css +3 -4
  51. package/test/lib/mock-server.js +133 -0
  52. package/test/proxy-tests.js +708 -0
  53. package/test/run.js +330 -0
  54. package/build/static/css/main.8bfa3d5f.css +0 -2
  55. package/build/static/css/main.8bfa3d5f.css.map +0 -1
  56. package/build/static/js/main.2247fb80.js.map +0 -1
  57. package/hack-of-anyproxy/lib/requestHandler.js +0 -1060
  58. /package/build/static/js/{main.2247fb80.js.LICENSE.txt → main.68f66be0.js.LICENSE.txt} +0 -0
@@ -5,7 +5,32 @@
5
5
  "Bash(git add:*)",
6
6
  "Bash(tshark:*)",
7
7
  "Bash(python3:*)",
8
- "Bash(gh issue create:*)"
8
+ "Bash(gh issue create:*)",
9
+ "Bash(docker system *)",
10
+ "Bash(grep -rn --include=\"*.js\" --include=\"*.ts\" --include=\"*.json\" -l \"benchmark\\\\|speed.test\\\\|speed_test\\\\|performance\\\\|latency\\\\|perf_test\\\\|perfTest\" /Users/hfy/jayli/block-proxy/ ! -path \"*/node_modules/*\")",
11
+ "Bash(grep -rn \"ping\\\\|latency\\\\|speed\\\\|perf\\\\|benchmark\\\\|timeout\" /Users/hfy/jayli/block-proxy/ --include=\"*.js\" --include=\"*.ts\" --include=\"*.sh\" ! -path \"*/node_modules/*\" ! -path \"*/build/*\")",
12
+ "Bash(oha *)",
13
+ "Bash(ping *)",
14
+ "Bash(brew install *)",
15
+ "Bash(npm run *)",
16
+ "Bash(pnpm i *)",
17
+ "Bash(break)",
18
+ "Bash(pkill -f \"node ./proxy/start.js\")",
19
+ "Bash(pkill -f \"node ./socks5/start.js\")",
20
+ "Bash(pkill -f \"node proxy/start.js\")",
21
+ "Bash(pkill -f \"node socks5/start.js\")",
22
+ "Bash(gh release *)",
23
+ "Read(//Library/Frameworks/Python.framework/Versions/**)",
24
+ "Bash(/Library/Frameworks/Python.framework/Versions/3.13/bin/python3 -m nuitka --version)",
25
+ "Bash(ps *)",
26
+ "Bash(pkill -f nuitka)",
27
+ "Bash(sips *)",
28
+ "Bash(git *)",
29
+ "Bash(command -v fswatch)",
30
+ "Bash(npm view *)",
31
+ "Bash(npx react-scripts *)",
32
+ "Bash(npm config *)",
33
+ "Bash(npm whoami *)"
9
34
  ]
10
35
  }
11
36
  }
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: "build-client"
3
+ description: "构建 macOS 客户端 SocksClient.app(仅本地构建,不升级版本号)"
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Skill: Build Client
8
+
9
+ 仅本地构建 macOS 客户端,不修改版本号,不发布。自动检测当前机器架构并构建对应版本。
10
+
11
+ 每次构建都会从 `client/icons/app_icon.png` 重新生成 `app.icns`,确保图标始终与源文件一致。
12
+
13
+ ## Instructions
14
+
15
+ 1. 执行构建脚本:
16
+ ```bash
17
+ bash client/build.sh
18
+ ```
19
+
20
+ 2. 构建完成后,报告结果:
21
+ - 架构:当前机器架构(由 `uname -m` 自动检测)
22
+ - 输出路径:`client/dist/SocksClient.app`
23
+ - 打包文件:`client/dist/SocksClient-macos-{arch}.zip`
24
+ - 告知用户构建是否成功
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: "release-client"
3
+ description: "构建并发布 macOS 客户端到 GitHub Release(自动从最新 release 版本号 patch +1)"
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Skill: Release Client
8
+
9
+ 构建并发布 macOS 客户端到 GitHub Release。支持多架构(arm64 / x86_64),当前机器编译当前架构。
10
+
11
+ ## Instructions
12
+
13
+ ### 1. 获取当前架构和版本信息
14
+
15
+ - **检测架构**:`uname -m`(x86_64 或 arm64)
16
+ - `x86_64` → zip 名 `SocksClient-macos-x86_64.zip`
17
+ - `arm64` → zip 名 `SocksClient-macos-arm64.zip`
18
+
19
+ - **获取最新版本号**:
20
+ ```bash
21
+ gh release list --repo jayli/block-proxy --limit 1 --json tagName --jq '.[0].tagName'
22
+ ```
23
+ - 如果没有任何 release,初始版本为 `v0.1.0`
24
+ - 如果有,取 tag(如 `v0.1.2`),将 patch 版本 +1(→ `v0.1.3`)
25
+
26
+ ### 2. 确认发布策略
27
+
28
+ 向用户展示并确认:
29
+ - 当前架构
30
+ - 新版本号(是否创建新 release,还是追加到已有 release)
31
+ - **默认行为**:patch +1 创建新 release。如果用户指定追加到已有 release(如「加到 v0.1.0 里」),则跳过版本号递增和 git 提交,直接编译并上传资产
32
+
33
+ ### 3. 版本号更新(仅新建 release 时)
34
+
35
+ 新版本号记为 `$VERSION`(不含 `v` 前缀):
36
+ - `client/VERSION` → 写入 `$VERSION`
37
+ - `client/build.sh` → 替换 `VERSION="..."` 为 `VERSION="$VERSION"`
38
+ - `client/app.py` → 替换所有 `版本:v...` 为 `版本:v$VERSION`
39
+
40
+ ### 4. 构建
41
+
42
+ ```bash
43
+ bash client/build.sh
44
+ ```
45
+
46
+ 构建脚本自动检测当前架构,输出对应的 zip 文件。
47
+
48
+ ### 5. 提交并推送(仅新建 release 时)
49
+
50
+ - `git add client/VERSION client/build.sh client/app.py`
51
+ - `git commit -m "release(client): v$VERSION"`
52
+ - `git push`
53
+
54
+ ### 6. 创建或更新 GitHub Release
55
+
56
+ **新建 release**:
57
+ ```bash
58
+ gh release create v$VERSION client/dist/SocksClient-macos-{arch}.zip --title "SocksClient v$VERSION" --notes "SocksClient v$VERSION (macOS {arch})"
59
+ ```
60
+
61
+ **追加资产到已有 release**(tag 记为 `$TAG`,如 `v0.1.0`):
62
+ ```bash
63
+ gh release upload $TAG client/dist/SocksClient-macos-{arch}.zip --repo jayli/block-proxy --clobber
64
+ ```
65
+
66
+ ### 7. 报告结果
67
+
68
+ 展示 release URL 给用户:`https://github.com/jayli/block-proxy/releases/tag/$VERSION`
package/CLAUDE.md CHANGED
@@ -13,7 +13,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with th
13
13
  - `npm run socks5` – Start SOCKS5 server only
14
14
  - `npm run cp` – Print start banner (used internally by other scripts)
15
15
 
16
- ### Code Analysis
16
+ ### Testing
17
+ - `npm run test:proxy` – 一键代理连通性/性能/吞吐量测试(需先启动代理服务)
17
18
  - `npm test` – Run React tests (currently limited, based on CRA defaults)
18
19
 
19
20
  ### Utilities
@@ -21,9 +22,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with th
21
22
 
22
23
  ### Build & Deployment
23
24
  - `npm run build` – Build React frontend
24
- - `npm run docker:build` – Build Docker image for current architecture
25
- - `npm run docker:build_arm` – Build ARM64 Docker image
26
- - `npm test` – Run React tests
25
+ - `npm run docker:build` – Build Docker image (amd64)
26
+ - `npm run docker:build:arm` – Build ARM64 Docker image
27
+ - `npm run docker:push` – Build and push amd64 + arm64 dual-arch to ACR (needs `docker login` first)
28
+ - `npm run docker:push:amd64` / `npm run docker:push:arm64` – Push single architecture
27
29
  - `npm run eject` – Eject from Create React App (irreversible)
28
30
 
29
31
  ### Global CLI
@@ -36,52 +38,28 @@ Block-Proxy is a MITM-based proxy filtering tool designed for parental control a
36
38
 
37
39
  ### Core Components
38
40
 
39
- 1. **Proxy Engine** (`/proxy/`)
40
- - `proxy.js` – Main AnyProxy integration with MITM logic, request/response filtering
41
- - `mitm/rule.js`MITM rule definitions (YouTube ads, Youdao Dictionary, etc.)
42
- - `mitm/youtube/` – YouTube ad-blocking response modifiers
43
- - `mitm/ydcd/` – Youdao Dictionary VIP modifier
44
- - `mitm/persistentStore.js` – Presistent store for MITM state (you can read along)
45
- - `mitm/uaFilter.js`User-Agent based filtering
46
- - `scan.js` – Network scanning for device discovery (every 2 hours via ARP)
47
- - `fs.js` Configuration file management (read/write/backup)
48
- - `attacker.js` – Request blocking logic
49
- - `domain.js` – Host pattern matching
50
- - `operator.js` – Proxy control operations (restart, etc.)
51
- - `http.js` – HTTP client utilities
52
- - `wanip.js` – WAN IP detection
53
- - `monitor.js` Proxy monitoring interface
54
-
55
- 2. **SOCKS5 Proxy** (`/socks5/`)
56
- - `server.js` – SOCKS5 over TLS implementation (port 8002), forwards to AnyProxy
57
- - `start.js` – SOCKS5 server entry point
58
-
59
- 3. **Backend Server** (`/server/`)
60
- - `express.js` – Express.js API server for admin interface (port 8004)
61
- - `start.js` – Main server entry point (decides whether to start admin UI based on config)
62
- - `util.js` – Shared utilities
63
-
64
- 4. **React Frontend** (`/src/`)
65
- - `App.js` – Admin interface for managing blocking rules
66
- - Built with Create React App, configured via CRACO
67
-
68
- 5. **CLI Interface** (`/bin/`)
69
- - `start.js` – Global CLI entry point with auto-restart capabilities (max 10000 restarts) and config cleanup on exit
70
-
71
- 6. **AnyProxy Fork** (`/hack-of-anyproxy/`)
72
- - Modified AnyProxy request handler with custom TLS handling, IPv6 normalization, and UA-based filtering
73
- - Patched into `@bachi/anyproxy` package at runtime
74
-
75
- 7. **Configuration** (`config.json`)
76
- - Runtime configuration: ports, blocked hosts, authentication, device list
77
- - Auto-saved from admin interface
78
- - Key fields: `block_hosts[]`, `proxy_port`, `socks5_port`, `enable_express`, `enable_socks5`, `devices[]`, `auth_username`, `auth_password`
41
+ 1. **Proxy Engine** (`/proxy/`) – 核心入口 `proxy.js`,集成 AnyProxy 实现 MITM 请求/响应过滤。`attacker.js` 执行拦截判断,`domain.js` 做 host 匹配,`fs.js` 管理 config.json 读写备份,`scan.js` 每 2 小时 ARP 扫描发现设备。`mitm/` 子目录包含规则定义(`rule.js`)和具体的响应修改器(YouTube 去广告、有道词典 VIP)
42
+
43
+ 2. **SOCKS5 Proxy** (`/socks5/`) SOCKS5 over TLS 实现(端口 8002),TLS 握手认证后通过 CONNECT 隧道转发至 AnyProxy
44
+
45
+ 3. **Backend Server** (`/server/`)Express API 服务器(端口 8003)。`start.js` 根据 config 决定启动完整栈还是仅代理模式
46
+
47
+ 4. **React Frontend** (`/src/`) CRA + CRACO 构建的管理界面,`App.js` 为主组件
48
+
49
+ 5. **Test Suite** (`/test/`) – `run.js` 一键测试入口(自动启动 Mock Server),`proxy-tests.js` 覆盖 HTTP/SOCKS5 连通性、延迟、并发、吞吐量
50
+
51
+ 6. **CLI** (`/bin/start.js`)全局命令入口,失败自动重启(max 10000 次),退出时清理全局配置
52
+
53
+ 7. **AnyProxy Fork** (`node_modules/@bachi/anyproxy/`)核心 MITM 代理引擎,fork 自 AnyProxy 的私有 npm 包(v0.1.5)。关键源码:`proxy.js`(入口)、`lib/requestHandler.js`(HTTP/HTTPS 请求处理与转发)、`lib/httpsServerMgr.js`(HTTPS MITM 服务管理、动态证书生成)、`lib/certMgr.js`(根证书与域名证书管理)。所有底层连接处理、TLS 拦截、请求转发的实现分析都应从此包查找
54
+
55
+ 8. **TLS Certificates** (`/cert/`) – `rootCA.key` + `rootCA.crt`,Docker 构建时复制到容器的 `~/.anyproxy/certificates/`,客户端需安装此证书才能 HTTPS MITM
56
+
57
+ 9. **Configuration** (`config.json`) – 运行时配置(非源码),由 `proxy/fs.js` 管理。关键字段:`block_hosts[]`, `proxy_port`, `socks5_port`, `enable_express`, `enable_socks5`, `devices[]`, `auth_username`, `auth_password`
79
58
 
80
59
  ### Port Configuration
81
60
  - `8001` – HTTP proxy port (mandatory, AnyProxy)
82
61
  - `8002` – SOCKS5 over TLS port (optional)
83
- - `8003` – AnyProxy monitoring interface (optional)
84
- - `8004` – Admin configuration interface (optional, Express)
62
+ - `8003` – Admin configuration interface (Express,原 AnyProxy 监控端口已永久关闭)
85
63
  - `3000` – React development server (dev only)
86
64
 
87
65
  ### Entry Points
@@ -92,8 +70,9 @@ Block-Proxy is a MITM-based proxy filtering tool designed for parental control a
92
70
  ### Request Flow
93
71
  ```
94
72
  Client → HTTP Proxy (8001) → AnyProxy → MITM Rules → Target Server
95
- → SOCKS5 (8002) → TLS → AnyProxyMITM Rules → Target Server
73
+ → SOCKS5 (8002) → TLS → SOCKS5 Server HTTP Proxy (8001) → Target Server
96
74
  ```
75
+ SOCKS5 先做 TLS 握手和认证,然后通过 CONNECT 命令建立隧道,将 TCP 流量转发至下游 HTTP 代理。
97
76
 
98
77
  ## Key Patterns
99
78
 
@@ -140,32 +119,55 @@ Client → HTTP Proxy (8001) → AnyProxy → MITM Rules → Target Server
140
119
 
141
120
  ### Development Workflow
142
121
  1. **Development**: `npm run dev` starts proxy + admin UI + SOCKS5 (if enabled)
143
- 2. **Frontend Development**: `npm run craco` starts React dev server (port 3000) with API proxy to backend (port 8003)
122
+ 2. **Frontend Development**: `npm run craco` starts React dev server (port 3000),CRACO `/api` 请求代理到 Express 后端端口 8003
144
123
  3. **Testing**: Proxy-only mode with `npm run proxy`
145
124
  4. **Building**: `npm run build` compiles React frontend to `/build/`
146
- 5. **Docker**: Separate commands for x86 and ARM architectures
125
+ 5. **Docker**: Dockerfile 基于 Node 18 Alpine(多阶段构建),本地开发可用更高版本 Node
147
126
 
148
127
  ### Dependencies
149
- **Note:** Due to the `@bachi/anyproxy` fork being incompatible with newer Node.js versions, it is bundled as a `devDependency`. Most runtime dependencies are in `devDependencies`:
150
- - `@bachi/anyproxy` – Modified AnyProxy fork for MITM
151
- - `express` Backend API server
152
- - `react`, `react-dom` Frontend framework
153
- - `commander` CLI argument parsing
154
- - `axios` HTTP client for API calls
155
- - `qrcode` Certificate QR code generation for MITM setup
156
- - `ping` – Network ping utility
157
- - `http-proxy-agent`, `https-proxy-agent` – Upstream proxy support
158
- - `@craco/craco` – CRA configuration override
128
+ **Note:** `@bachi/anyproxy` 与较新 Node.js 版本不完全兼容,因此连同大多数运行时依赖一起放在 `devDependencies` 中(查看 package.json 了解完整列表)
129
+
130
+ **核心依赖 `@bachi/anyproxy`(v0.1.5):** fork 自开源 AnyProxy 的私有 npm 包,是整个代理系统的底层引擎。分析连接处理、TLS 拦截、请求转发等底层逻辑时,应直接阅读 `node_modules/@bachi/anyproxy/` 的源码(而非项目根目录下的其他备份)。关键文件:
131
+ - `proxy.js` 包入口,创建代理服务器实例
132
+ - `lib/requestHandler.js` HTTP/HTTPS 请求处理与转发核心
133
+ - `lib/httpsServerMgr.js` HTTPS MITM 服务管理、动态伪造证书
134
+ - `lib/certMgr.js` 根证书与域名证书的生成和管理
135
+
136
+ ## macOS Client (`/client/`)
137
+
138
+ macOS 状态栏代理客户端,纯 Python 实现,连接远端 SOCKS5 over TLS 服务。
139
+
140
+ ### Commands
141
+ - `python main.py` – 直接运行客户端(开发模式)
142
+ - `bash build.sh` – Nuitka 一键构建 macOS .app(输出到 `dist/SocksClient.app`)
143
+ - `cd client && pytest tests/` – 运行客户端单元测试
144
+ - 删除 `icons/app.icns` 后再 `bash build.sh` 可强制重新生成应用图标
145
+
146
+ ### Architecture
147
+ ```
148
+ main.py (入口, 文件锁单实例) → app.py (rumps 状态栏 App)
149
+ ├── proxy_core.py (asyncio SOCKS5/HTTP 代理核心)
150
+ ├── config.py (配置读写, ~/Library/Application Support/SocksClient/)
151
+ ├── config_window.py (tkinter 配置窗口, 独立进程启动)
152
+ └── system_proxy.py (macOS 系统代理 networksetup)
153
+ ```
154
+
155
+ ### Key Design Decisions
156
+ - **纯 Python 替代 xray-core**:公司安全软件(云壳)按二进制特征码拦截 xray-core、py2app、PyInstaller,因此用 asyncio + ssl 模块纯 Python 实现 SOCKS5 over TLS 协议,用 Nuitka 编译为原生二进制
157
+ - **代理协议流程**:本地应用 → 本地 SOCKS5(1080)/HTTP(1087) → TLS 连接远端 → SOCKS5 握手(用户名密码认证) → CONNECT → 双向 relay
158
+ - **私有地址直连**:127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 等私有地址段不走代理,直接连接(可通过配置项关闭)
159
+ - **UI 线程安全**:代理启动/停止在后台线程执行,UI 更新通过 `AppHelper.callAfter()` 调度回主线程
160
+ - **config_window.py 作为独立进程**:tkinter 窗口通过 `subprocess.Popen` 用系统 Python 启动(Nuitka 编译后 `sys.executable` 不是 Python 解释器),关闭后自动检测配置变化并重启代理
161
+ - **Nuitka 构建后处理**:`build.sh` 自动重命名可执行文件、修正 Info.plist(CFBundleExecutable、LSUIElement 等)
159
162
 
160
163
  ## Important Notes
161
- - SOCKS5 proxy does not support MAC address targeting (only HTTP proxy does)
162
- - Clients must install AnyProxy certificate for HTTPS MITM inspection
163
- - Service needs network scanning permissions (best deployed on OpenWRT gateway, uses `arp -a`)
164
- - Admin interface allows real-time rule management with proxy restart
165
- - Docker builds use Chinese npm registry (registry.npmmirror.com) by default
166
- - iOS Safari has security restriction: proxy with auth cannot be same as gateway IP
167
- - Network device table refreshes every 2 hours; new devices may need manual refresh
168
- - HTTP keep-alive enabled with max 100 sockets for performance
164
+ - **Testing 陷阱**: 通过代理请求 `127.0.0.1` 会被 AnyProxy 拦截返回管理页面。Mock Server 需绑定 `0.0.0.0` 并通过 LAN IP 访问
165
+ - SOCKS5 不支持 MAC 地址定向拦截(仅 HTTP 代理支持)
166
+ - 客户端必须安装 `cert/` 目录下的 AnyProxy 证书才能启用 HTTPS MITM(URL 路径过滤、广告重写)。未安装证书时,将 `enable_mitm` 设为 `"0"` 可切换为纯隧道转发模式,关闭所有 MITM 解密和拦截,零证书错误
167
+ - Docker 构建默认使用 npmmirror.com 镜像源
168
+ - iOS Safari 安全限制:带认证的代理不能和网关 IP 相同
169
+ - 路由表每 2 小时刷新;新设备可能需要手动刷新
170
+ - ACR 推送前需先 `docker login --username=hi50078584@aliyun.com crpi-x1zji86f6jpcd7t1.cn-hangzhou.personal.cr.aliyuncs.com`
169
171
 
170
172
  # Project Rules & Skills
171
173
 
package/Dockerfile CHANGED
@@ -45,7 +45,7 @@ COPY cert/rootCA.crt /root/.anyproxy/certificates/
45
45
 
46
46
  USER nodeuser
47
47
 
48
- EXPOSE 8001 8002 8003
48
+ EXPOSE 8001 8002
49
49
 
50
50
  # 使用 node 启动脚本作为 CMD
51
51
  CMD ["npm", "run", "start"]
package/README.md CHANGED
@@ -14,8 +14,9 @@ Socks5/http 代理工具,支持 MITM 和二次开发
14
14
  - HTTP 代理 + Socks5 over TLS 代理
15
15
  - 域名拦截、url 正则、Mac 地址拦截
16
16
  - 设定日期和时间段、顺便过滤广告
17
+ - 提供服务端和客户端
17
18
 
18
- ### 1)使用方法
19
+ ### 1)服务端使用方法
19
20
 
20
21
  #### ① 方式一:快速部署
21
22
 
@@ -41,11 +42,13 @@ block-proxy -c rule.js
41
42
 
42
43
  #### ② 方式二,Docker 部署(推荐)
43
44
 
44
- 1. 下载 Docker 文件
45
- - Arm 架构 → <a href="http://yui.cool:7001/public/downloads/block-proxy/arm/block-proxy.tar" target=_blank>block-proxy-arm.tar</a>
46
- - X86 架构 → <a href="http://yui.cool:7001/public/downloads/block-proxy/x86/block-proxy-x86.tar" target=_blank>block-proxy-x86.tar</a>
47
- 2. 导入:`docker load < block-proxy.tar`
48
- 3. 启动:
45
+ 1. 拉取镜像(自动匹配架构):
46
+
47
+ ```
48
+ docker pull crpi-x1zji86f6jpcd7t1.cn-hangzhou.personal.cr.aliyuncs.com/lijing00333/block-proxy:latest
49
+ ```
50
+
51
+ 2. 启动:
49
52
 
50
53
  ```
51
54
  docker run --init -d --restart=unless-stopped \
@@ -57,7 +60,7 @@ docker run --init -d --restart=unless-stopped \
57
60
  --cpus="5" \
58
61
  --memory 400m \
59
62
  -v "$(pwd)/":/app/config \
60
- --name block-proxy block-proxy
63
+ --name block-proxy crpi-x1zji86f6jpcd7t1.cn-hangzhou.personal.cr.aliyuncs.com/lijing00333/block-proxy:latest
61
64
  ```
62
65
 
63
66
  其中挂载目录 `$(pws)/` 下的 `rule.js` 是需要额外挂载的配置文件,可留空。
@@ -71,16 +74,16 @@ docker run --init -d --restart=unless-stopped \
71
74
  ```
72
75
  docker run --init -d --restart=unless-stopped --user=root \
73
76
  -v "$(pwd)/":/app/config \
74
- -e TZ=Asia/Shanghai -p 8001:8001 -p 8002:8002 -p 8003:8003 \
75
- --name block-proxy block-proxy
77
+ -e TZ=Asia/Shanghai -p 8001:8001 -p 8002:8002 \
78
+ --name block-proxy crpi-x1zji86f6jpcd7t1.cn-hangzhou.personal.cr.aliyuncs.com/lijing00333/block-proxy:latest
76
79
  ```
77
80
 
78
- ### 2)端口配置
81
+ ### 2)服务侧端口配置
79
82
 
80
- 1. 服务端配置:配置面板 <http://server-ip:8004>,关闭、启用配置面板:<http://server-ip:8001>
81
- 2. 客户端配置:http 代理直接在 iphone wifi 详情里手动配置,socks5 代理只支持 socks5 over TLS,用小火箭配置。配置信息参照[配置面板](http://localhost:8004)
83
+ 1. 服务端配置:配置面板 <http://server-ip:8003>,关闭、启用配置面板:<http://server-ip:8001>
84
+ 2. 客户端配置:http 代理直接在 iphone wifi 详情里手动配置,socks5 代理只支持 socks5 over TLS,用小火箭配置。配置信息参照[配置面板](http://localhost:8003)
82
85
 
83
- ### 3)开发和调试
86
+ ### 3)服务侧代码的开发和调试
84
87
 
85
88
  代码 clone 下来后执行`pnpm i`,执行 `npm run dev` 运行本地服务。默认开启 5 个端口:
86
89
 
@@ -89,11 +92,11 @@ docker run --init -d --restart=unless-stopped --user=root \
89
92
  |3000 |调试端口(仅开发调试配置面板用)| 生产环境不启用|
90
93
  |8001 |HTTP 代理端口 | 不可禁用 |
91
94
  |8002 |Socks5(Over TLS)代理端口 | 可禁用 |
92
- |8003 |AnyProxy 监控页面| 可禁用 |
93
- |8004 |后台配置页端口 | 可禁用 |
95
+ |8003 |后台配置页端口 | 可禁用 |
96
+ |8004 |~~已废弃~~ | 原后台配置页,已迁移至 8003 |
94
97
 
95
98
 
96
- ### 4)Docker 构建说明
99
+ ### 4)Block-Proxy 的 Docker 构建说明
97
100
 
98
101
  准备工作,构建 docker 包,先启动本地 Docker:
99
102
 
@@ -101,16 +104,21 @@ docker run --init -d --restart=unless-stopped --user=root \
101
104
  - 生产启动:`npm run start`,生产环境使用
102
105
  - 只启动代理:`npm run proxy`,不启动配置后台,只启动代理
103
106
  - 后台构建:`npm run build`
104
- - 本地打包:`npm run docker:build`
105
- - 打arm包:`npm run docker:build_arm`
106
- - 导出tar包到本地:`docker save -o block-proxy.tar block-proxy`
107
- - 安装包到openwrt:`docker load < block-proxy.tar`
108
-
107
+ - 本地构建 amd64:`npm run docker:build`
108
+ - 本地构建 arm64:`npm run docker:build:arm`
109
+ - 推送 amd64 + arm64 双架构到 ACR:`npm run docker:push`
110
+ - 仅推送 amd64:`npm run docker:push:amd64`
111
+ - 仅推送 arm64:`npm run docker:push:arm64`
112
+
113
+ > 首次使用 `docker:push` 前需要先 ACR 登录:
114
+ > ```
115
+ > docker login --username=hi50078584@aliyun.com crpi-x1zji86f6jpcd7t1.cn-hangzhou.personal.cr.aliyuncs.com
116
+ > ```
109
117
  > 要是打包 docker 空间不够就执行 `docker system prune -a --volumes`
110
118
 
111
119
  拷贝 tar 到 openwrt 后启动容器:参照上文 Docker部署。
112
120
 
113
- ### 5)配置说明
121
+ ### 5)客户端配置说明
114
122
 
115
123
  #### ① 代理端口
116
124
 
@@ -123,7 +131,7 @@ docker run --init -d --restart=unless-stopped --user=root \
123
131
 
124
132
  #### ② 后台配置
125
133
 
126
- 访问路径:`http://proxy-ip:8004`
134
+ 访问路径:`http://proxy-ip:8003`
127
135
 
128
136
  路由表间隔两小时刷新一次。如果新加入网的设备没生效,刷新一下路由表。添加限制条件后,点击重启代理按钮。
129
137
 
@@ -151,7 +159,7 @@ ip6tables -I forwarding_rule -m mac --mac-source D2:9E:8D:1B:F1:4E -j REJECT
151
159
  然后重启防火墙
152
160
 
153
161
 
154
- ### 6)更多信息
162
+ ### 6)关于 MITM
155
163
 
156
164
  #### 应用条件:
157
165
 
@@ -194,6 +202,12 @@ done!
194
202
  >
195
203
  ><img width="300" alt="image" src="https://github.com/user-attachments/assets/0f46d6b4-00b1-44aa-9be7-fa23a09bb199" />
196
204
 
205
+ ### 7)客户端软件
206
+
207
+ 提供了客户端桌面端连接代理工具 SocksClient.app,支持 MacOS(M系列)。
208
+
209
+ 下载地址:[GitHub Release](https://github.com/jayli/block-proxy/releases/latest)
210
+
197
211
  ### License
198
212
 
199
213
  MIT
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "files": {
3
- "main.css": "/static/css/main.8bfa3d5f.css",
4
- "main.js": "/static/js/main.2247fb80.js",
3
+ "main.css": "/static/css/main.3f317ce6.css",
4
+ "main.js": "/static/js/main.68f66be0.js",
5
5
  "static/js/835.f7452062.chunk.js": "/static/js/835.f7452062.chunk.js",
6
6
  "index.html": "/index.html",
7
- "main.8bfa3d5f.css.map": "/static/css/main.8bfa3d5f.css.map",
8
- "main.2247fb80.js.map": "/static/js/main.2247fb80.js.map",
7
+ "main.3f317ce6.css.map": "/static/css/main.3f317ce6.css.map",
8
+ "main.68f66be0.js.map": "/static/js/main.68f66be0.js.map",
9
9
  "835.f7452062.chunk.js.map": "/static/js/835.f7452062.chunk.js.map"
10
10
  },
11
11
  "entrypoints": [
12
- "static/css/main.8bfa3d5f.css",
13
- "static/js/main.2247fb80.js"
12
+ "static/css/main.3f317ce6.css",
13
+ "static/js/main.68f66be0.js"
14
14
  ]
15
15
  }
package/build/index.html CHANGED
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.2247fb80.js"></script><link href="/static/css/main.8bfa3d5f.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.68f66be0.js"></script><link href="/static/css/main.3f317ce6.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
@@ -0,0 +1,2 @@
1
+ body{background:#f8fafc}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}:root{--primary:#4f46e5;--primary-hover:#4338ca;--primary-light:#eef2ff;--success:#059669;--success-hover:#047857;--danger:#dc2626;--danger-hover:#b91c1c;--warning:#d97706;--warning-hover:#b45309;--info:#0284c7;--info-hover:#0369a1;--gray-50:#f9fafb;--gray-100:#f3f4f6;--gray-200:#e5e7eb;--gray-300:#d1d5db;--gray-400:#9ca3af;--gray-500:#6b7280;--gray-600:#4b5563;--gray-700:#374151;--gray-800:#1f2937;--gray-900:#111827;--radius:8px;--radius-sm:6px;--radius-xs:4px;--shadow-sm:0 1px 2px #0000000d;--shadow:0 1px 3px #0000001a,0 1px 2px #0000000f;--shadow-md:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--transition:150ms cubic-bezier(0.4,0,0.2,1)}*{box-sizing:border-box}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;margin:0}.App{background:linear-gradient(135deg,#f0f4ff,#f8fafc 30%,#f0fdf4 70%,#fefce8);min-height:100vh;padding:24px 16px 48px}.config-container{margin:0 auto;max-width:960px;position:relative}.config-container h1{color:#1f2937;color:var(--gray-800);font-size:26px;font-weight:700;letter-spacing:-.02em;margin:0 0 28px;text-align:center}.config-section{background:#fff;border:1px solid #f3f4f6;border:1px solid var(--gray-100);border-radius:12px;box-shadow:0 1px 3px #0000001a,0 1px 2px #0000000f;box-shadow:var(--shadow);margin-bottom:20px;padding:24px 28px;transition:box-shadow .15s cubic-bezier(.4,0,.2,1);transition:box-shadow var(--transition)}.config-section:hover{box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;box-shadow:var(--shadow-md)}.config-section h2{align-items:center;border-bottom:2px solid #f3f4f6;border-bottom:2px solid var(--gray-100);color:#1f2937;color:var(--gray-800);display:flex;font-size:16px;font-weight:600;gap:8px;margin:0 0 18px;padding-bottom:12px}.config-section h2:before{background:#4f46e5;background:var(--primary);border-radius:2px;content:"";display:inline-block;height:18px;width:4px}.config-section h3{color:#374151;color:var(--gray-700);font-size:15px;font-weight:600;margin:0 0 12px}.server-info p{color:#4b5563;color:var(--gray-600);font-size:14px;margin:4px 0}.server-info strong{color:#1f2937;color:var(--gray-800)}.ip-list{grid-gap:8px;display:grid;gap:8px;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));list-style:none;margin:12px 0 0;padding:0}.ip-item{align-items:center;background:#f9fafb;background:var(--gray-50);border:1px solid #f3f4f6;border:1px solid var(--gray-100);border-radius:6px;border-radius:var(--radius-sm);display:flex;font-size:13px;gap:8px;padding:8px 12px}.interface-name{color:#374151;color:var(--gray-700);font-weight:600;white-space:nowrap}.ip-address{background:#eef2ff;background:var(--primary-light);border-radius:4px;border-radius:var(--radius-xs);color:#4f46e5;color:var(--primary);font-family:SF Mono,Fira Code,JetBrains Mono,Courier New,monospace}.docker-info,.ip-address{font-size:12px;font-weight:500;padding:2px 8px}.docker-info{background:#e0f2fe;border-radius:10px;color:#0284c7;color:var(--info);display:inline-block;margin-left:6px}.host-ip-info{border-top:1px solid #e5e7eb;border-top:1px solid var(--gray-200);margin-top:12px;padding-top:12px}.host-ip-info p{color:#4b5563;color:var(--gray-600);font-size:13px}.host-input{align-items:flex-end;display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}.host-input input[type=text]{background:#fff;border:1.5px solid #d1d5db;border:1.5px solid var(--gray-300);border-radius:6px;border-radius:var(--radius-sm);flex:1 1;font-size:14px;min-width:180px;padding:10px 14px;transition:border-color .15s cubic-bezier(.4,0,.2,1),box-shadow .15s cubic-bezier(.4,0,.2,1);transition:border-color var(--transition),box-shadow var(--transition)}.host-input input[type=text]:focus{border-color:#4f46e5;border-color:var(--primary);box-shadow:0 0 0 3px #4f46e51a;outline:none}.host-input input[type=text]::placeholder{color:#9ca3af;color:var(--gray-400)}.time-inputs{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.time-inputs label{align-items:center;color:#4b5563;color:var(--gray-600);display:flex;font-size:13px;gap:4px}.time-inputs label span{white-space:nowrap}.host-input button,.time-inputs button{background:#4f46e5;background:var(--primary);border:none;border-radius:6px;border-radius:var(--radius-sm);color:#fff;cursor:pointer;font-size:14px;font-weight:500;padding:10px 20px;transition:background .15s cubic-bezier(.4,0,.2,1),transform 50ms;transition:background var(--transition),transform 50ms;white-space:nowrap}.host-input button:hover{background:#4338ca;background:var(--primary-hover)}.host-input button:active{transform:scale(.97)}hr.simple-line{background:#e5e7eb;background:var(--gray-200);border:none;height:1px;margin:0 0 12px}.host-list{list-style:none;margin:0;padding:0}.host-item{align-items:center;display:flex;gap:12px;justify-content:space-between;padding:14px 16px;transition:background .15s cubic-bezier(.4,0,.2,1);transition:background var(--transition)}.host-item,.host-item:first-child{border-radius:6px;border-radius:var(--radius-sm)}.host-item:first-child{background:#f9fafb;background:var(--gray-50);color:#6b7280;color:var(--gray-500);font-size:13px;font-weight:600;letter-spacing:.05em;margin-bottom:4px;padding:10px 16px;text-transform:uppercase}.host-item:not(:first-child){border-bottom:1px solid #f3f4f6;border-bottom:1px solid var(--gray-100)}.host-item:not(:first-child):hover{background:#f9fafb;background:var(--gray-50)}.host-item:last-child{border-bottom:none}.host-info{align-items:center;display:flex;flex-grow:1;flex-wrap:wrap;gap:12px}span.host-text{flex:1 1;font-size:14px;line-height:1.5;min-width:140px}span.host-text strong{color:#1f2937;color:var(--gray-800);font-weight:600}input[type=time]{background:#fff;border:1.5px solid #d1d5db;border:1.5px solid var(--gray-300);border-radius:4px;border-radius:var(--radius-xs);font-family:inherit;font-size:13px;padding:6px 8px;transition:border-color .15s cubic-bezier(.4,0,.2,1),box-shadow .15s cubic-bezier(.4,0,.2,1);transition:border-color var(--transition),box-shadow var(--transition)}input[type=time]:focus{border-color:#4f46e5;border-color:var(--primary);box-shadow:0 0 0 3px #4f46e51a;outline:none}.time-controls{align-items:center;display:flex;gap:6px}.time-controls label{align-items:center;color:#6b7280;color:var(--gray-500);display:flex;font-size:13px}.remove-btn{align-items:center;background:#fff;border:1.5px solid #e5e7eb;border:1.5px solid var(--gray-200);border-radius:4px;border-radius:var(--radius-xs);color:#9ca3af;color:var(--gray-400);cursor:pointer;display:flex;font-size:14px;font-weight:600;height:30px;justify-content:center;min-width:30px;padding:0;transition:all .15s cubic-bezier(.4,0,.2,1);transition:all var(--transition);width:30px}.remove-btn:hover{background:#dc2626;background:var(--danger);border-color:#dc2626;border-color:var(--danger);color:#fff}.setting-row{align-items:center;display:flex;gap:16px;margin-bottom:14px}.setting-row label{color:#374151;color:var(--gray-700);flex-shrink:0;font-size:14px;font-weight:500;text-align:right;width:200px}.setting-row input[type=number],.setting-row input[type=text],.setting-row select{background:#fff;border:1.5px solid #d1d5db;border:1.5px solid var(--gray-300);border-radius:6px;border-radius:var(--radius-sm);flex:1 1;font-family:inherit;font-size:14px;padding:10px 14px;transition:border-color .15s cubic-bezier(.4,0,.2,1),box-shadow .15s cubic-bezier(.4,0,.2,1);transition:border-color var(--transition),box-shadow var(--transition)}.setting-row input[type=number]:focus,.setting-row input[type=text]:focus,.setting-row select:focus{border-color:#4f46e5;border-color:var(--primary);box-shadow:0 0 0 3px #4f46e51a;outline:none}.setting-row select{-webkit-appearance:none;appearance:none;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-position:right 12px center;background-repeat:no-repeat;cursor:pointer;padding-right:36px}.setting-row.full-width{flex-wrap:wrap}.setting-row.full-width label{width:200px}.setting-row.full-width input{flex:1 1;min-width:240px}.help-text{color:#9ca3af;color:var(--gray-400);font-size:12px;line-height:1.5;margin-left:216px;margin-top:-4px;width:100%}.actions{border-top:1px solid #f3f4f6;border-top:1px solid var(--gray-100);display:flex;gap:12px;margin-top:20px;padding-top:20px}.refresh-btn,.restart-btn,.save-btn{border:none;border-radius:6px;border-radius:var(--radius-sm);cursor:pointer;flex:1 1;font-size:14px;font-weight:600;letter-spacing:.01em;padding:12px 20px;transition:all .15s cubic-bezier(.4,0,.2,1);transition:all var(--transition)}.save-btn{background:#059669;background:var(--success);color:#fff}.save-btn:hover:not(:disabled){background:#047857;background:var(--success-hover);box-shadow:0 2px 8px #0596694d}.restart-btn{background:#d97706;background:var(--warning);color:#fff}.restart-btn:hover:not(:disabled){background:#b45309;background:var(--warning-hover);box-shadow:0 2px 8px #d977064d}.refresh-btn:disabled,.restart-btn:disabled,.save-btn:disabled{background:#d1d5db;background:var(--gray-300);box-shadow:none;color:#6b7280;color:var(--gray-500);cursor:not-allowed}.refresh-btn:active:not(:disabled),.refresh-table-btn:active:not(:disabled),.restart-btn:active:not(:disabled),.save-btn:active:not(:disabled){transform:scale(.98)}.refresh-btn{background:#0284c7;background:var(--info);color:#fff}.refresh-btn:hover:not(:disabled){background:#0369a1;background:var(--info-hover);box-shadow:0 2px 8px #0284c74d}.refresh-table-btn{margin-top:12px}.config-section p{color:#4b5563;color:var(--gray-600);font-size:14px;line-height:1.7;margin:6px 0}.config-section p b{color:#1f2937;color:var(--gray-800);font-weight:600}.config-section a{color:#4f46e5;color:var(--primary);font-weight:500;text-decoration:none;transition:color .15s cubic-bezier(.4,0,.2,1);transition:color var(--transition)}.config-section a:hover{color:#4338ca;color:var(--primary-hover);text-decoration:underline}#qrcode{background:#fff;border:1px solid #e5e7eb;border:1px solid var(--gray-200);border-radius:6px;border-radius:var(--radius-sm);margin-top:8px;padding:8px}button{font-family:inherit}.config-section>button{background:#fff;border:1.5px solid #d1d5db;border:1.5px solid var(--gray-300);border-radius:6px;border-radius:var(--radius-sm);color:#374151;color:var(--gray-700);cursor:pointer;font-size:14px;font-weight:500;padding:10px 20px;transition:all .15s cubic-bezier(.4,0,.2,1);transition:all var(--transition)}.config-section>button:hover:not(:disabled){background:#f9fafb;background:var(--gray-50);border-color:#9ca3af;border-color:var(--gray-400)}.config-section>button:disabled{cursor:not-allowed;opacity:.5}.weekday-controls{gap:2px}.weekday-btn,.weekday-controls{align-items:center;display:flex}.weekday-btn{background:#f3f4f6;background:var(--gray-100);border:1.5px solid #0000;border-radius:4px;border-radius:var(--radius-xs);color:#6b7280;color:var(--gray-500);cursor:pointer;font-size:12px;font-weight:500;height:30px;justify-content:center;padding:0;transition:all .15s cubic-bezier(.4,0,.2,1);transition:all var(--transition);width:30px}.weekday-btn:hover{background:#e5e7eb;background:var(--gray-200);color:#374151;color:var(--gray-700)}.weekday-btn.active{background:#4f46e5;background:var(--primary);border-color:#4f46e5;border-color:var(--primary);box-shadow:0 1px 3px #4f46e54d;color:#fff}.weekday-btn:focus{box-shadow:0 0 0 2px #4f46e54d;outline:none}.mac-input{align-items:center;display:flex;gap:8px}.mac-input input[type=text]{border:1.5px solid #d1d5db;border:1.5px solid var(--gray-300);border-radius:4px;border-radius:var(--radius-xs);font-family:SF Mono,Fira Code,JetBrains Mono,Courier New,monospace;font-size:12px;padding:6px 10px;transition:border-color .15s cubic-bezier(.4,0,.2,1),box-shadow .15s cubic-bezier(.4,0,.2,1);transition:border-color var(--transition),box-shadow var(--transition);width:130px}.mac-input input[type=text]:focus{border-color:#4f46e5;border-color:var(--primary);box-shadow:0 0 0 3px #4f46e51a;outline:none}.mac-input input[type=text]::placeholder{color:#9ca3af;color:var(--gray-400);font-family:inherit}.table-right-blank{min-width:30px}.title-mac-input{font-size:13px;min-width:138px;text-align:center}.title-time-controls{font-size:13px;min-width:190px;text-align:center}.title-weedkey-controls{font-size:13px;min-width:120px}.toast{align-items:center;animation:toastSlideIn .35s cubic-bezier(.16,1,.3,1);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-radius:10px;box-shadow:0 10px 25px #00000026;color:#fff;display:flex;font-size:14px;font-weight:500;gap:12px;min-width:260px;padding:14px 20px;position:fixed;right:24px;top:24px;z-index:1000}@keyframes toastSlideIn{0%{opacity:0;transform:translateX(120%)}to{opacity:1;transform:translateX(0)}}.toast.success{background:#059669;background:var(--success);box-shadow:0 8px 20px #0596694d}.toast.error{background:#dc2626;background:var(--danger);box-shadow:0 8px 20px #dc26264d}.toast.info{background:#0284c7;background:var(--info);box-shadow:0 8px 20px #0284c74d}.toast-close{align-items:center;background:#fff3;border:none;border-radius:50%;color:#fff;cursor:pointer;display:flex;flex-shrink:0;font-size:18px;font-weight:400;height:24px;justify-content:center;margin-left:auto;padding:0;transition:background .15s cubic-bezier(.4,0,.2,1);transition:background var(--transition);width:24px}.toast-close:hover{background:#ffffff59}.config-section .ip-list{display:flex;flex-direction:column;gap:4px}.config-section .ip-item{background:#f9fafb;background:var(--gray-50)}@media (max-width:768px){.App{padding:12px 8px 32px}.config-section{padding:18px 16px}.host-info{gap:8px}.host-info,.setting-row{align-items:flex-start;flex-direction:column}.setting-row{gap:6px}.setting-row label{text-align:left;width:100%}.setting-row.full-width label{width:100%}.help-text{margin-left:0}.actions,.host-item{flex-direction:column}.host-item{align-items:flex-start;gap:8px}.remove-btn{align-self:flex-end}.time-controls{flex-wrap:wrap}.host-input{flex-direction:column}.host-input input[type=text]{min-width:100%}.ip-list{grid-template-columns:1fr}}@media (max-width:480px){.config-container h1{font-size:20px}.config-section h2{font-size:14px}.weekday-btn{font-size:11px;height:26px;width:26px}}
2
+ /*# sourceMappingURL=main.3f317ce6.css.map*/
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static/css/main.3f317ce6.css","mappings":"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","sources":["index.css","App.css"],"sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n background: #f8fafc;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;\n}\n","/* ===== 基础变量与全局 ===== */\n:root {\n --primary: #4f46e5;\n --primary-hover: #4338ca;\n --primary-light: #eef2ff;\n --success: #059669;\n --success-hover: #047857;\n --danger: #dc2626;\n --danger-hover: #b91c1c;\n --warning: #d97706;\n --warning-hover: #b45309;\n --info: #0284c7;\n --info-hover: #0369a1;\n --gray-50: #f9fafb;\n --gray-100: #f3f4f6;\n --gray-200: #e5e7eb;\n --gray-300: #d1d5db;\n --gray-400: #9ca3af;\n --gray-500: #6b7280;\n --gray-600: #4b5563;\n --gray-700: #374151;\n --gray-800: #1f2937;\n --gray-900: #111827;\n --radius: 8px;\n --radius-sm: 6px;\n --radius-xs: 4px;\n --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);\n --shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);\n --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n/* ===== 布局 ===== */\n.App {\n min-height: 100vh;\n background: linear-gradient(135deg, #f0f4ff 0%, #f8fafc 30%, #f0fdf4 70%, #fefce8 100%);\n padding: 24px 16px 48px;\n}\n\n.config-container {\n max-width: 960px;\n margin: 0 auto;\n position: relative;\n}\n\n.config-container h1 {\n text-align: center;\n font-size: 26px;\n font-weight: 700;\n color: var(--gray-800);\n margin: 0 0 28px;\n letter-spacing: -0.02em;\n}\n\n/* ===== Section 卡片 ===== */\n.config-section {\n background: white;\n border-radius: 12px;\n padding: 24px 28px;\n margin-bottom: 20px;\n box-shadow: var(--shadow);\n border: 1px solid var(--gray-100);\n transition: box-shadow var(--transition);\n}\n\n.config-section:hover {\n box-shadow: var(--shadow-md);\n}\n\n.config-section h2 {\n margin: 0 0 18px;\n font-size: 16px;\n font-weight: 600;\n color: var(--gray-800);\n padding-bottom: 12px;\n border-bottom: 2px solid var(--gray-100);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.config-section h2::before {\n content: '';\n display: inline-block;\n width: 4px;\n height: 18px;\n background: var(--primary);\n border-radius: 2px;\n}\n\n.config-section h3 {\n margin: 0 0 12px;\n font-size: 15px;\n font-weight: 600;\n color: var(--gray-700);\n}\n\n/* ===== 服务器信息 ===== */\n.server-info p {\n margin: 4px 0;\n color: var(--gray-600);\n font-size: 14px;\n}\n\n.server-info strong {\n color: var(--gray-800);\n}\n\n.ip-list {\n list-style: none;\n padding: 0;\n margin: 12px 0 0;\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n gap: 8px;\n}\n\n.ip-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--gray-50);\n border-radius: var(--radius-sm);\n font-size: 13px;\n border: 1px solid var(--gray-100);\n}\n\n.interface-name {\n font-weight: 600;\n color: var(--gray-700);\n white-space: nowrap;\n}\n\n.ip-address {\n font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', 'Courier New', monospace;\n font-size: 12px;\n background: var(--primary-light);\n color: var(--primary);\n padding: 2px 8px;\n border-radius: var(--radius-xs);\n font-weight: 500;\n}\n\n.docker-info {\n display: inline-block;\n font-size: 12px;\n font-weight: 500;\n color: var(--info);\n background: #e0f2fe;\n padding: 2px 8px;\n border-radius: 10px;\n margin-left: 6px;\n}\n\n.host-ip-info {\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--gray-200);\n}\n\n.host-ip-info p {\n font-size: 13px;\n color: var(--gray-600);\n}\n\n/* ===== 拦截主机列表 ===== */\n.host-input {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-bottom: 16px;\n align-items: flex-end;\n}\n\n.host-input input[type=\"text\"] {\n flex: 1;\n min-width: 180px;\n padding: 10px 14px;\n border: 1.5px solid var(--gray-300);\n border-radius: var(--radius-sm);\n font-size: 14px;\n transition: border-color var(--transition), box-shadow var(--transition);\n background: white;\n}\n\n.host-input input[type=\"text\"]:focus {\n outline: none;\n border-color: var(--primary);\n box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);\n}\n\n.host-input input[type=\"text\"]::placeholder {\n color: var(--gray-400);\n}\n\n.time-inputs {\n display: flex;\n gap: 10px;\n align-items: center;\n flex-wrap: wrap;\n}\n\n.time-inputs label {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 13px;\n color: var(--gray-600);\n}\n\n.time-inputs label span {\n white-space: nowrap;\n}\n\n.host-input button,\n.time-inputs button {\n padding: 10px 20px;\n background: var(--primary);\n color: white;\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n transition: background var(--transition), transform 50ms;\n white-space: nowrap;\n}\n\n.host-input button:hover {\n background: var(--primary-hover);\n}\n\n.host-input button:active {\n transform: scale(0.97);\n}\n\nhr.simple-line {\n height: 1px;\n border: none;\n background: var(--gray-200);\n margin: 0 0 12px;\n}\n\n/* ===== 域名列表表头 ===== */\n.host-list {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n\n.host-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: var(--radius-sm);\n transition: background var(--transition);\n}\n\n.host-item:first-child {\n background: var(--gray-50);\n border-radius: var(--radius-sm);\n margin-bottom: 4px;\n padding: 10px 16px;\n font-weight: 600;\n font-size: 13px;\n color: var(--gray-500);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.host-item:not(:first-child) {\n border-bottom: 1px solid var(--gray-100);\n}\n\n.host-item:not(:first-child):hover {\n background: var(--gray-50);\n}\n\n.host-item:last-child {\n border-bottom: none;\n}\n\n.host-info {\n display: flex;\n flex-grow: 1;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\nspan.host-text {\n font-size: 14px;\n flex: 1;\n min-width: 140px;\n line-height: 1.5;\n}\n\nspan.host-text strong {\n color: var(--gray-800);\n font-weight: 600;\n}\n\n/* ===== 时间控件 ===== */\ninput[type=\"time\"] {\n padding: 6px 8px;\n border: 1.5px solid var(--gray-300);\n border-radius: var(--radius-xs);\n font-size: 13px;\n background: white;\n transition: border-color var(--transition), box-shadow var(--transition);\n font-family: inherit;\n}\n\ninput[type=\"time\"]:focus {\n outline: none;\n border-color: var(--primary);\n box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);\n}\n\n.time-controls {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.time-controls label {\n display: flex;\n align-items: center;\n font-size: 13px;\n color: var(--gray-500);\n}\n\n/* ===== 删除按钮 ===== */\n.remove-btn {\n background: white;\n color: var(--gray-400);\n border: 1.5px solid var(--gray-200);\n width: 30px;\n height: 30px;\n min-width: 30px;\n cursor: pointer;\n border-radius: var(--radius-xs);\n font-size: 14px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all var(--transition);\n padding: 0;\n}\n\n.remove-btn:hover {\n background: var(--danger);\n color: white;\n border-color: var(--danger);\n}\n\n/* ===== 设置行 ===== */\n.setting-row {\n display: flex;\n align-items: center;\n margin-bottom: 14px;\n gap: 16px;\n}\n\n.setting-row label {\n width: 200px;\n text-align: right;\n font-weight: 500;\n color: var(--gray-700);\n flex-shrink: 0;\n font-size: 14px;\n}\n\n.setting-row input[type=\"text\"],\n.setting-row input[type=\"number\"],\n.setting-row select {\n flex: 1;\n padding: 10px 14px;\n border: 1.5px solid var(--gray-300);\n border-radius: var(--radius-sm);\n font-size: 14px;\n font-family: inherit;\n transition: border-color var(--transition), box-shadow var(--transition);\n background: white;\n}\n\n.setting-row input[type=\"text\"]:focus,\n.setting-row input[type=\"number\"]:focus,\n.setting-row select:focus {\n outline: none;\n border-color: var(--primary);\n box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);\n}\n\n.setting-row select {\n cursor: pointer;\n appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 12px center;\n padding-right: 36px;\n}\n\n.setting-row.full-width {\n flex-wrap: wrap;\n}\n\n.setting-row.full-width label {\n width: 200px;\n}\n\n.setting-row.full-width input {\n flex: 1;\n min-width: 240px;\n}\n\n.help-text {\n width: 100%;\n margin-left: 216px;\n font-size: 12px;\n color: var(--gray-400);\n line-height: 1.5;\n margin-top: -4px;\n}\n\n/* ===== 操作按钮行 ===== */\n.actions {\n display: flex;\n gap: 12px;\n margin-top: 20px;\n padding-top: 20px;\n border-top: 1px solid var(--gray-100);\n}\n\n.save-btn,\n.restart-btn,\n.refresh-btn {\n flex: 1;\n padding: 12px 20px;\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n font-size: 14px;\n font-weight: 600;\n transition: all var(--transition);\n letter-spacing: 0.01em;\n}\n\n.save-btn {\n background: var(--success);\n color: white;\n}\n\n.save-btn:hover:not(:disabled) {\n background: var(--success-hover);\n box-shadow: 0 2px 8px rgba(5, 150, 105, 0.3);\n}\n\n.restart-btn {\n background: var(--warning);\n color: white;\n}\n\n.restart-btn:hover:not(:disabled) {\n background: var(--warning-hover);\n box-shadow: 0 2px 8px rgba(217, 119, 6, 0.3);\n}\n\n.save-btn:disabled,\n.restart-btn:disabled,\n.refresh-btn:disabled {\n background: var(--gray-300);\n color: var(--gray-500);\n cursor: not-allowed;\n box-shadow: none;\n}\n\n.save-btn:active:not(:disabled),\n.restart-btn:active:not(:disabled),\n.refresh-btn:active:not(:disabled),\n.refresh-table-btn:active:not(:disabled) {\n transform: scale(0.98);\n}\n\n.refresh-btn {\n background: var(--info);\n color: white;\n}\n\n.refresh-btn:hover:not(:disabled) {\n background: var(--info-hover);\n box-shadow: 0 2px 8px rgba(2, 132, 199, 0.3);\n}\n\n.refresh-table-btn {\n margin-top: 12px;\n}\n\n/* ===== 代理设置信息区 ===== */\n.config-section p {\n margin: 6px 0;\n font-size: 14px;\n color: var(--gray-600);\n line-height: 1.7;\n}\n\n.config-section p b {\n color: var(--gray-800);\n font-weight: 600;\n}\n\n.config-section a {\n color: var(--primary);\n text-decoration: none;\n font-weight: 500;\n transition: color var(--transition);\n}\n\n.config-section a:hover {\n color: var(--primary-hover);\n text-decoration: underline;\n}\n\n#qrcode {\n margin-top: 8px;\n border-radius: var(--radius-sm);\n border: 1px solid var(--gray-200);\n padding: 8px;\n background: white;\n}\n\n/* ===== 通用按钮 ===== */\nbutton {\n font-family: inherit;\n}\n\n.config-section > button {\n padding: 10px 20px;\n background: white;\n color: var(--gray-700);\n border: 1.5px solid var(--gray-300);\n border-radius: var(--radius-sm);\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n transition: all var(--transition);\n}\n\n.config-section > button:hover:not(:disabled) {\n background: var(--gray-50);\n border-color: var(--gray-400);\n}\n\n.config-section > button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* ===== 星期几按钮 ===== */\n.weekday-controls {\n display: flex;\n gap: 2px;\n align-items: center;\n}\n\n.weekday-btn {\n width: 30px;\n height: 30px;\n padding: 0;\n background: var(--gray-100);\n color: var(--gray-500);\n font-size: 12px;\n font-weight: 500;\n border-radius: var(--radius-xs);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1.5px solid transparent;\n transition: all var(--transition);\n}\n\n.weekday-btn:hover {\n background: var(--gray-200);\n color: var(--gray-700);\n}\n\n.weekday-btn.active {\n background: var(--primary);\n color: white;\n border-color: var(--primary);\n box-shadow: 0 1px 3px rgba(79, 70, 229, 0.3);\n}\n\n.weekday-btn:focus {\n outline: none;\n box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.3);\n}\n\n/* ===== MAC 地址输入 ===== */\n.mac-input {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.mac-input input[type=\"text\"] {\n width: 130px;\n padding: 6px 10px;\n border: 1.5px solid var(--gray-300);\n border-radius: var(--radius-xs);\n font-size: 12px;\n font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', 'Courier New', monospace;\n transition: border-color var(--transition), box-shadow var(--transition);\n}\n\n.mac-input input[type=\"text\"]:focus {\n outline: none;\n border-color: var(--primary);\n box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);\n}\n\n.mac-input input[type=\"text\"]::placeholder {\n color: var(--gray-400);\n font-family: inherit;\n}\n\n/* ===== 列表表头 ===== */\n.table-right-blank {\n min-width: 30px;\n}\n\n.title-mac-input {\n font-size: 13px;\n text-align: center;\n min-width: 138px;\n}\n\n.title-time-controls {\n font-size: 13px;\n text-align: center;\n min-width: 190px;\n}\n\n.title-weedkey-controls {\n font-size: 13px;\n min-width: 120px;\n}\n\n/* ===== Toast 通知 ===== */\n.toast {\n position: fixed;\n top: 24px;\n right: 24px;\n padding: 14px 20px;\n border-radius: 10px;\n color: white;\n font-weight: 500;\n font-size: 14px;\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);\n z-index: 1000;\n display: flex;\n align-items: center;\n gap: 12px;\n animation: toastSlideIn 0.35s cubic-bezier(0.16, 1, 0.3, 1);\n min-width: 260px;\n backdrop-filter: blur(8px);\n}\n\n@keyframes toastSlideIn {\n from {\n transform: translateX(120%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n.toast.success {\n background: var(--success);\n box-shadow: 0 8px 20px rgba(5, 150, 105, 0.3);\n}\n\n.toast.error {\n background: var(--danger);\n box-shadow: 0 8px 20px rgba(220, 38, 38, 0.3);\n}\n\n.toast.info {\n background: var(--info);\n box-shadow: 0 8px 20px rgba(2, 132, 199, 0.3);\n}\n\n.toast-close {\n background: rgba(255,255,255,0.2);\n border: none;\n color: white;\n font-size: 18px;\n font-weight: 400;\n cursor: pointer;\n padding: 0;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n transition: background var(--transition);\n margin-left: auto;\n flex-shrink: 0;\n}\n\n.toast-close:hover {\n background: rgba(255,255,255,0.35);\n}\n\n/* ===== 路由表 ===== */\n.config-section .ip-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.config-section .ip-item {\n background: var(--gray-50);\n}\n\n/* ===== 响应式 ===== */\n@media (max-width: 768px) {\n .App {\n padding: 12px 8px 32px;\n }\n\n .config-section {\n padding: 18px 16px;\n }\n\n .host-info {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n\n .setting-row {\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n }\n\n .setting-row label {\n width: 100%;\n text-align: left;\n }\n\n .setting-row.full-width label {\n width: 100%;\n }\n\n .help-text {\n margin-left: 0;\n }\n\n .actions {\n flex-direction: column;\n }\n\n .host-item {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n\n .remove-btn {\n align-self: flex-end;\n }\n\n .time-controls {\n flex-wrap: wrap;\n }\n\n .host-input {\n flex-direction: column;\n }\n\n .host-input input[type=\"text\"] {\n min-width: 100%;\n }\n\n .ip-list {\n grid-template-columns: 1fr;\n }\n}\n\n@media (max-width: 480px) {\n .config-container h1 {\n font-size: 20px;\n }\n\n .config-section h2 {\n font-size: 14px;\n }\n\n .weekday-btn {\n width: 26px;\n height: 26px;\n font-size: 11px;\n }\n}\n"],"names":[],"sourceRoot":""}