diff --git a/webui/src/components/settings/RemoteBrowserPanel.tsx b/webui/src/components/settings/RemoteBrowserPanel.tsx new file mode 100644 index 0000000..a299c98 --- /dev/null +++ b/webui/src/components/settings/RemoteBrowserPanel.tsx @@ -0,0 +1,97 @@ +import { useMemo, useState } from 'react' +import { ExternalLink, MonitorPlay, RotateCw } from 'lucide-react' + +import { Button } from '@/components/ui/button' + +/** + * 服务器上那个浏览器的**实时画面** —— 就是定时任务接管的那一个。 + * + * 用途只有一个:**过验证码**。抖音/小红书弹滑块或短信验证时,采集会静默失败 + * (网关回 200 + 空 body,看起来像「这个博主没作品」),人在这个画面里点一下就好了, + * 不用跑到机器跟前。 + * + * 走 noVNC(websockify 在宿主机上起的静态站)。 + * + * **别把它当成「看采集结果」的地方** —— 结果是作品栏和报表,这里是浏览器本身。 + */ + +// websockify 监听的端口。和 18051 这个应用不是同一个,所以**必须**是绝对地址。 +const VNC_PORT = 6080 + +// 特意**留空**:服务端那边密码去掉之后,地址里不传 password 就不会弹框。 +// +// 千万不要把密码写死在这里 —— 它会进 git 历史,还会打进前端产物,而任何能打开 +// 这个页面的人都能看到。要密码就打进 noVNC 自己的框里,或者去服务端把密码去掉。 +const VNC_PASSWORD = '' + +/** 拼 noVNC 的地址。主机名取自当前页面,所以换域名/IP 访问都能用。 */ +function viewerUrl(host: string, password: string): string { + const params = new URLSearchParams({ + autoconnect: '1', + // 让远程画面缩放到容器大小,而不是撑出滚动条。 + resize: 'scale', + // 掉线自己重连,省得每次断线都要手动点。 + reconnect: '1', + reconnect_delay: '2000', + }) + if (password) params.set('password', password) + return `http://${host}:${VNC_PORT}/vnc.html?${params.toString()}` +} + +export function RemoteBrowserPanel() { + const [nonce, setNonce] = useState(0) + + const url = useMemo( + () => viewerUrl(window.location.hostname, VNC_PASSWORD), + // nonce 变化 = 用户点了「重连」,靠换 key 强制 iframe 重新加载。 + // eslint-disable-next-line react-hooks/exhaustive-deps + [nonce], + ) + + return ( +
+
+ + 远程浏览器 + + 采集用的那个 · 弹验证码时在这里点 + +
+ + +
+
+ + {/* iframe 而不是 window.open:要的就是「抬眼就能看见」,而不是再开一个标签页。 + noVNC 那边不发 X-Frame-Options,所以嵌得进来。 */} +