Skip to content

Web Console

HadesTop edited this page Aug 23, 2026 · 7 revisions

Web 管理端

带登录的网页界面:看运行状态、查请求流、试一试某个 URL、改配置、管集群节点、 装卸可选组件。

ipclick run -w

# 同一目录起第二个实例:gRPC 与 Web 两个端口都要岔开
ipclick run --port 9628 --web-port 9531 -w
==============================================================
  IPClick Web 管理端: http://127.0.0.1:9527/
  用户名: admin
  密码:   N8mSKkdPbpiGzB128At3

  ⚠️ 该密码为本次启动随机生成,重启后失效。
==============================================================

配置

[WEB]
enabled = false        # 命令行 -w / --web 会覆盖它
port = 9527            # 命令行 --web-port 会覆盖它
host = "127.0.0.1"     # 默认只监听本机

登录凭据是机密,走 .env

IPCLICK_WEB_USER=admin
IPCLICK_WEB_PASSWORD=<一串够长的>

都不设的话每次启动随机生成密码并打印到控制台。 不预置 admin/admin 是刻意的—— 默认弱口令是这类管理界面被打穿的头号原因。

⚠️ 默认只监听 127.0.0.1 这个界面后面就是一个能代发任意请求的服务, 不该默认对外。要远程访问请用 SSH 隧道:

ssh -L 9527:127.0.0.1:9527 user@server

或者放在做了 TLS 终止的反向代理之后——它本身是明文 HTTP,密码会在网络上裸奔

可信局域网里自用(拿手机看看跑得怎么样)可以 ipclick run -w --web-lan (等价于 --web-host 0.0.0.0,也可以用 --web-host 只绑某一张网卡)。 此时启动会打两条明文告警。跨网段仍然请走隧道或反代。

布局与主题

三栏 CSS Grid:

┌────────┬────────────────────────┬──────────┐
│ 左导航  │ 主内容                  │ 右状态栏  │
│ 六个页面│                        │(仅总览页)│
│ 主题切换│                        │          │
└────────┴────────────────────────┴──────────┘

把"当前状态"常驻在右栏,主区留给要动手的内容——否则总览页会把服务器信息、各适配器、 渲染引擎、集群、最近请求全挤在一条竖线上,只能一路往下滚。

窄屏会分两级降级:先把右栏移到主内容下面,再把左导航压成顶部横条。

主题是两态的:亮 / 暗,选择记在 localStorage,刷新不丢。服务端默认值是 [WEB].theme,而浏览器里点过的那一下优先于它——反过来的话,用户每刷新一次页面, 自己刚选的主题就会被配置文件推翻一次。

刻意没有"跟随系统"这一档:它靠 CSS 的 prefers-color-scheme,而那一位取决于浏览器 读不读得到桌面偏好——Linux 上要 GTK 或 xdg-desktop-portal 配好才认,读不到就静默按亮色 处理。一个在半数机器上不生效、失败时又毫无迹象的选项,比没有这个选项更糟。

页面

总览 /

主区里每 5 秒自动更新的只有这两块(只换那一块,不重载整页):

  • 请求统计:总数、成功率、平均耗时、在途与峰值、累计流量、运行时长
  • 状态码分布条

下面这几块随整页渲染,要刷新页面才更新:

  • 各适配器的请求数 / 成功 / 失败 / 平均耗时 / 流量
  • 最近请求
  • 集群拓扑与各节点健康状况。每行带一个「摘除 / 恢复」按钮,可以就地手动把某台 节点摘出轮询(POST /action,带 CSRF)——发版或排查时不用改配置文件

右栏常驻:

  • 服务端:监听地址、本节点 id、运行模式、worker 数、默认适配器、压缩策略、配置文件路径
  • 安全:TLS、令牌鉴权、SSRF 两个开关、页内 JS、集群内部鉴权
  • 链路记录:数据来源、内存缓冲、落盘条数与文件大小、丢弃条数、保留天数
  • 限流:按 host 的并发与 QPS 上限
  • 可选组件:五个 extras 的状态速览,点进去就是组件页

组件的状态是两级的(包 / 浏览器本体),因为处理动作不同:一个是 pip install, 一个是 camoufox fetch,混成"不可用"会让人查错方向。

请求流 /trace

每个请求一条记录,实时刷新:默认每 5 秒,页面上可切 关闭 / 1 秒 / 5 秒 / 30 秒。 档位会写回地址栏,所以收藏或分享出去的链接带着这个设置。

刷的是局部:只替换表格那一块的 HTML,不重载整页。整页重来会丢滚动位置、 冲掉正在填的过滤条件、还会白闪一下。渲染仍然在服务端(/fragment/trace), 所以不存在"JS 里那份模板和 Python 这份对不上"的问题。

说明
时间 完成时刻
状态 状态码。传输故障显示成红色的「失败」标签(/api/trace 的 JSON 里才是 -1
方法 GET / POST / …
URL [TRACE].record_url = false 时只显示 host
适配器 实际用了哪个(browser 会显示解析后的具体引擎)
节点 实际执行的节点 id —— 集群转发是否生效看这里
耗时 毫秒
大小 响应体字节数
(末列) 标记位:转发 / 流式 / 重试 N / 排队 Nms

支持按状态、适配器、URL 关键词、条数筛选(节点只是表格里的一列,不能筛)。 状态可选:全部 / 只看失败 / 2xx / 3xx / 4xx / 5xx / 连接失败。

关键词是字面匹配。 URL 里 %_ 很常见(/api_v2/utm_source=), 底层 SQL 的 LIKE 会把它们当通配符,所以查询前做了转义——搜 api_v2 不会匹配到 apiXv2

开了 [TRACE].sqlite_enabled 才能查历史和跨天统计;不开就只有内存里最近 memory_size 条(默认 500),进程重启即丢。详见链路记录

试一试 /test

输入一个 URL 直接发请求,看链路源码。适合验证配置改对了没有、 某个站点用哪个适配器能过。

能填的和 SDK 的 request() 基本一一对应:方法、适配器、超时、重试次数与退避、 请求头、cookie、查询参数、请求体(raw 或 JSON)、允许的状态码、verify、 是否跟随重定向、指纹伪装(仅 curl_cffi)、是否走代理、目标节点,以及 automation_configautomation_script(后者需要服务端 allow_scripts = true)。

结果显示状态码、耗时、响应头、执行节点、重试次数,以及响应体源码

从 curl 导入

浏览器 DevTools 里对着请求「复制为 cURL」,粘进输入框就自动填好 URL / 方法 / 请求头 / 请求体。手动把十几个 header 逐个拆进表单既慢又容易漏,而漏掉一个 header 往往就是"为什么我用 IPClick 抓不到、浏览器却可以"的答案

认得 -X / -H / -d / --data-raw / -b / -A / -e / -m / --url 等常用参数, 以及 --compressed-sSL 这类合并短开关。认不出或没法对应的会明确列出来 (比如 -F 文件上传、-k 跳过证书校验)——静默丢掉一个参数比不支持它更糟, 用户会以为已经导入了。

短参数贴值的写法一并认(-XPOST-m30-H'X: y'),值里带 = 也不影响: -H'Cookie: sid=abc'-d'user=alice&p=1'-b'sid=abc'$'...'(DevTools 在值里有转义字符时的输出)里,字面中文、\xNN 字节转义、\uHHHH 三种都能还原。不带协议但带查询串的地址(example.com/api?q=1)也认。

⚠️ 上面这三类在含 2.0.0 在内的已发布版本里会被读成另一个意思,且不报错: 贴值参数只要值里有 = 就整条被当成"未识别"丢掉(提示里还会报出一个并不存在的 参数名,-d 的方法也退回 GET);$'{"name":"张三"}' 静默变成 {"name":"??"}; 带查询串的无协议地址被判成"没找到网址"。都是 2.0.0 之后修的。

指定目标节点

开了服务端转发时多一个"目标节点"下拉,选中后跳过负载均衡强制打到那一台。

这是为了验证新加的机器配对没有:按策略选就只能反复点、靠轮询碰运气命中, 节点一多完全没法用。刻意走内部路径而不改协议——点名是诊断能力,不该变成正式的 路由语义(那就要回答"点名的节点挂了要不要转移""能不能穿透多跳"这些问题)。

没有故障转移是有意的:点名就是点名,转到别的机器上会让这次验证失去意义。

失败时的报错也是人话,不是一坨 _InactiveRpcError 的 repr:

转发到节点 node-c 失败 —— UNAUTHENTICATED:缺少或无效的鉴权令牌。
集群内部鉴权不通过:两端的 IPCLICK_CLUSTER_SECRET 必须完全一致
(在一台机器上生成,原样复制到其余机器的 .env)

两条限制

都是刻意的:

  • 源码最多显示 256 KB。 再多浏览器渲染会卡,而看源码这件事看前几十 KB 基本够判断。
  • 超时上限 120 秒。 页面是同步等结果的,让它能等十分钟等于给自己留一个占满 worker 的口子。

用的是 Post/Redirect/Get:提交后重定向再展示,刷新页面不会重复发请求。

组件 /components

五个可选 extras 的安装状态与装 / 卸,按「HTTP 适配器 / 浏览器渲染」分两组。

每个组件显示两级状态,两级都要看:

显示 含义 该做什么
未装 Python 包不在 点「安装」,或 pip install "ipclick[xxx]"
缺本体 包装了,浏览器本体没下 点「下载浏览器本体」,或跑各自的 fetch/install
本体未知 包在,但探不出本体状态(多半装坏了,或引擎依赖的子模块导不进来) 重装这个组件,或手动跑一次它的 fetch / install
可用 两级都就绪

"网页能在机器上装东西"值得停一下。 这里的判断是:一个已经能改配置、能代发任意 请求的管理端,再加上"装它自己声明过的那五个可选依赖"并没有实质性地扩大攻击面, 而省掉的"开一个终端 → 找到那个 venv → 敲对命令 → 重启进程"是真实的痛点。

放开这一项不等于放松要求,实现上守着六条:

  1. 包名全部来自白名单常量,绝不拼接用户输入。 表单里传来的 extra 必须能在组件 清单里查到,查不到直接拒绝。命令以列表形式交给 subprocessshell=False), 连"引号转义写错"这个类别都不存在。
  2. 绑定当前解释器。 不依赖 PATH 上的 pip,也不依赖"当前激活的 venv"。装到 别的环境去比装不上更糟——装完页面还是看不到,人会以为这个功能坏了。
  3. pipuv pip 两条路都支持,自动探测。 uv 创建的 venv 默认不装 pip (IPClick 自己的开发环境就是),此时 python -m pip 会报 No module named pip; 反过来非 uv 环境里又没有 uv 命令。两者都没有时明确报错并给出手动命令。
  4. 长任务不占 HTTP 请求。 camoufox fetch 要下约 1 GB,跑在后台线程里,页面 轮询状态并实时显示输出。否则请求超时、用户以为失败、然后重复点击叠加下载。
  5. 错误原样透出。 系统 Python 下没有写权限时 pip 会失败,那条 Permission denied 本身就是答案,笼统的"安装失败"等于把答案藏起来。
  6. 卸载只卸 Python 包。 pip uninstall camoufox 不会删掉 ~/.cache/camoufox 里 那 1 GB 浏览器本体。界面把它的路径和体积摆出来让人自己决定——从网页上递归删 一个 GB 级目录是不可逆操作,风险和收益完全不成比例。

装的不是 ipclick[extra],而是从本机 ipclick 自己的元数据(Requires-Dist)读出 来的依赖列表。装 ipclick[extra] 会把 ipclick 自身拖进解析:不钉版本可能被"升级" 覆盖掉正在运行的这份(开发环境的可编辑安装尤其容易),钉了版本又要求该版本能在索引 上找到——本地开发版必然卡住。

元数据实在读不出来时会回落ipclick[extra]==<当前版本>,页面会把这件事写在 任务输出的第一行。这是兜底而非常态:走到这条路的本地开发版基本会失败,但至少失败 的原因是明说出来的。

「刷新状态」:不重启也能看到装卸

探测用的是 importlib.util.find_spec() + invalidate_caches():不执行模块代码、 能立刻看到新装的包,而且能正确反映卸载。真正的 import 推迟到"要启动浏览器了"那一刻。

不能用模块级的 try: import X 那样结论会在进程启动那一刻就固化:在终端里装完 camoufox,Web 端刷新多少次都还是"未装";卸载更是完全探测不出来——真 import 过的模块 留在 sys.modules 里,删掉磁盘上的包也不会让它消失。

页面上有手动「刷新状态」按钮;装 / 卸任务结束后也会自动刷一次,并同步适配器注册表 ——刚装好的 niquests 立刻就能在「试一试」里选。

刻意不做成"每次加载页面都重新探测":那要动 importlib.reload,在一个正在服务 请求的进程里重载 camoufox(它还牵着 playwright)风险很高。

配置 /config

改白名单内的行为配置,写回 ipclick.toml

写入方式:定点文本替换 → 保留注释与格式;改动前留 .bak; 写临时文件再 os.replace → 断电不会留下半个配置文件。

每一项标了是否需要重启生效。日志级别、调试模式、only_errorsrecord_url 这些是热生效的;max_workers、监听地址这些要重启。

可改的一共 75 项,分 12 组

分组
服务端 gRPC / Web 端口与监听地址、worker 线程数、工作进程数、在途 RPC 上限、单连接并发流上限、响应压缩、异步模式、调试模式
日志 级别、输出位置、格式、单文件上限、保留个数
下载行为 单次请求超时、连接超时、跟随系统代理、流式分片大小
重试 最大次数、退避基数、退避指数、单次等待上限
连接池 连接池总上限(niquests 读它;curl_cffi 在异步模式下当作会话并发上限)、长连接保活上限(只有 niquests 读
按 host 限流 并发上限、等待超时、闲置回收、最多跟踪多少 host、QPS 上限、突发额度
浏览器渲染 启用开关、引擎、无头、并发页面上限、三个超时、内核、wait_until、视口、UA、语言、指纹跟随代理、沙箱开关、代理网关
代理 主机、端口、协议、隧道接入地址与三个隧道参数
链路记录 内存条数、SQLite 开关与路径、保留天数、只记失败、落盘队列容量、记录完整 URL
集群 本节点 id、负载均衡、故障转移次数、转发超时、探活间隔与超时、摘除与恢复阈值
Web 管理端 页面主题
客户端与压缩 RPC 重试与退避、请求压缩策略与门槛

生成凭据

鉴权令牌、Web 密码、集群共享密钥各能生成一个随机值,只显示一次,页面上直接给出 可以粘进 .env 的那一行。

"只显示一次"是真的:服务端不保存、不写进任何文件,值只在那一次响应里出现过, 刷新就没了。这条规矩(机密不接受从本页写入)顺带让"不可再次查看"自动成立—— 没抄下来就再生成一个。

生成时会区分两类,因为它们的后续动作完全不同:

类型 提示
本机独有(鉴权令牌、Web 密码) 生成即用,改完重启本进程即可,不用同步
集群共享密钥 必须原样复制到所有其他节点的 .env

后者不说清楚就会出事:每台机器各自生成一个,派生出来的节点令牌互不匹配, 转发会全部 UNAUTHENTICATED

集群节点管理

节点管理是配置页的一个分页(/config?tab=cluster)。 /nodes 这个老地址会自动跳过去——节点管理本来就是集群配置的一部分。

集群节点列表的增删改,同样写回 ipclick.toml。提交前会校验地址格式、id 唯一性等, 校验不过就整批拒绝——写进去一半是最难排查的状态。

节点的 token 不在这里管。 令牌由集群共享密钥派生,见集群

保存即生效,不用重启

前提:单进程模式[SERVER].processes = 1,默认值)。多进程时 Web 只跑在 0 号进程上,只更新它会让各 worker 的路由与鉴权状态分裂,所以配置只写盘不热更新, 页面会明说"需重启 ipclick 才会在全部 worker 生效"。而"工作进程数"本身就是配置页 上可改的一项,所以这个前提很容易在不知不觉间失效。

单进程下保存会原地重建 ClusterConfigNodePool:新节点立刻参与转发轮询, 被移除 / 改了地址的节点连接会被关掉。

开着服务端转发时,重建按 id 复用已有的节点状态——直接重建会把每个节点的健康计数 清零,那样"连续 N 次才切状态"的判定永远达不到,熔断与恢复双双失效。

热更新覆盖的是不涉及监听端口的部分:节点列表、权重、策略、阈值。监听地址属于 gRPC server 的构造参数,那个换不了——但改节点列表本来也不会动到端口。

DNS 发现模式([CLUSTER.discovery].mode = "dns")下节点是解析出来的, [CLUSTER].nodes 只是占位,热更新只改策略与阈值、不动节点列表。

测试连接

每行一个按钮,就地验连通性集群内部鉴权不发业务请求

否则加完节点只能等真实流量转发过去才发现连不上,而那时错误已经混在业务失败里了。

结论分四种,因为排查方向完全不同:

结果 含义 去查什么
连不上 端口不通或对端没起来 进程、防火墙、地址写没写对
鉴权不通过 连上了,但令牌不匹配 各节点 .env 里的 IPCLICK_CLUSTER_SECRET 是否完全一致
通过(对方未设防) 连上了,但对端根本没启用鉴权 那台机器任何人都能调,补上共享密钥
通过 连上了、令牌对、对端也开了鉴权 不用查了

为什么不能只用健康检查grpc.health.v1 刻意免鉴权(编排系统的探针通常拿不到 密钥),所以在它眼里"那台机器没起来"和"起来了但我的令牌不对"长得一模一样。所以探两 层——健康检查回答"连得上吗",Ping RPC(走鉴权、不做任何业务动作)回答"令牌对吗"。

第三种情况需要对端自报一位 auth_required:探测成功本身分不清"我的令牌对"和 "它根本不验"。

对端没有 Ping 会怎样? 会返回 UNIMPLEMENTED。但能走到方法查找这一步, 说明鉴权拦截器已经放行了——所以页面会如实说"连得上、鉴权也通过,但对方不认识 这个方法",而不是误报成鉴权失败。滚动升级期间这个区分很重要。

地址栏里还没保存的地址也能直接测——加完一行想先试试通不通是最自然的动作, 非要先保存才能测就把流程割断了。

AI 接入 /skill

给 AI 代理用的技能包页:装它的命令、SKILL.md 全文、以及原文下载(/skill.md)。

技能包是一份随 wheel 分发的 Markdown,讲清楚什么时候该用 IPClick、--json 的输出 契约、以及几个最容易踩的坑。装完之后直接对代理说"用 ipclick 抓一下 …"即可, 不必每次再逐条解释。命令行侧对应 ipclick skill show / install / path, 详见命令行

页面存在的理由和「从 curl 导入」一样:把一份要交给别的程序的东西摆出来给人看一眼。 代理读到的到底是什么内容,不该只能靠翻源码确认。

能改什么,不能改什么

不可从网页修改

  • [SECURITY] 全部 —— 鉴权令牌、TLS、SSRF 防护
  • Web 管理端自己的登录凭据
  • 集群共享密钥与各节点 token
  • [BROWSER].allow_scripts

理由:这些是安全边界本身。一个能改安全边界的管理界面,等于把整条防线的强度降到 "这个界面的登录有多牢"。攻破 Web 登录不该等于能关掉 SSRF 防护、能拿到令牌、 能打开任意 JS 执行。这几项只能改配置文件 + 重启——那需要机器上的访问权限。

同样地,Web 管理端从不显示任何机密。总览页只说"鉴权:已启用",不显示令牌值。 「生成凭据」也不破这条:生成的值只在那一次响应里出现,服务端不留副本。

唯一放开的那一项:能装 / 卸 IPClick 自己声明的五个可选组件。这条线仍然是划着 的——包名走白名单常量、命令用列表交给 subprocess、绑定当前解释器,做不到"执行 任意命令"。判断依据是:改超时、加节点、装一个自己声明过的可选依赖,这类操作改错了 也只是性能或可用性问题,可逆、可见;而关掉 SSRF 拦截、改掉令牌是不可逆的暴露。

安全设计

措施 说明
默认只监听 127.0.0.1 见上文
无默认口令 不设就随机生成并打印
CSRF 令牌 所有 POST 都校验
HttpOnly + SameSite=Strict 会话 cookie 防 XSS 窃取与跨站提交
CSP default-src 'none' 页面不加载任何外部资源
CSP script-src脚本哈希 见下
connect-src 'self' 页面里的 fetch 只能打本源
登录失败限速 防暴力破解
不显示机密 见上文
安全边界不可网页修改 见上文

关于页面里的 JavaScript

页面里的 JS 是三百多行两段常量,只做六件没它不行的事:

  • 手动切主题(记在 localStorage
  • 弹窗确认
  • 复制到剪贴板
  • 就地 POST:「测试连接」「装 / 卸依赖」
  • 按看的人的浏览器时区渲染时间 —— 服务端跑在 UTC 容器里时,东八区的人看到的仍是 自己的钟点。这件事只能在浏览器里做
  • 实时刷新与档位切换

除此之外一行不多写。

前端仍然没有构建链路:没有模板引擎、框架、打包工具,也没有任何外部资源。 布局是纯 CSS Grid,脚本是源码里的两段常量。

CSP 里 script-src 用的是那两段脚本的 sha256 哈希,不是 'unsafe-inline' 差别是实打实的:万一某处转义漏了、注入进一行 <script>,哈希对不上就执行不了; 而 'unsafe-inline' 会把这层保护整个让开。

style-src 仍然是 'unsafe-inline'——页面里有大量 style="width:37%" 这类属性 (分布条、趋势图的宽度),哈希覆盖不了行内样式属性,而样式注入的危害远小于脚本注入。

页面里也没有任何内联事件属性onclick= 之类),全部用 addEventListener + data-* 绑定——用事件属性就得在 CSP 里开 'unsafe-hashes',等于把口子重新开一条。

API

返回 JSON,都需要同一个会话 cookie——不是免鉴权的开放接口。

接口 用途
/api/status 完整运行状态快照
/api/trace 链路记录查询(支持与页面相同的过滤参数)
/api/components/status 当前安装任务的状态与输出
/api/components/action 装 / 卸 / 下载浏览器本体(POST + CSRF)
/api/nodes/probe 探测某个节点(POST + CSRF)
/action 页面上的写操作总入口,如总览页的节点摘除 / 恢复(POST + CSRF)
/skill.md 下载 SKILL.md 原文
/deploy/deploy.zip 子节点部署材料:生成 toml / .env / 启动命令,或打成 ZIP

另有两个返回 HTML 片段的内部接口,供页面局部刷新用,不建议外部依赖: /fragment/dashboard/fragment/trace

curl -c c.txt -d "username=admin&password=..." http://127.0.0.1:9527/login
curl -b c.txt http://127.0.0.1:9527/api/trace?limit=50

和集群状态页的区别

Web 管理端 [WEB] 集群状态页
怎么起 ipclick run -w,配置在 [WEB] 代码里 StatusPageServer(...).start()没有配置节
默认端口 9527 9529
登录
能改东西 能(白名单内) 不能,纯只读
内容 全局状态、请求流、试一试、组件、配置、节点 只有集群节点与健康状态

两个都默认只监听本机。

下一步

Clone this wiki locally