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 ( +
+ 连不上就说明宿主机上的 websockify 没在跑(`{window.location.hostname}:{VNC_PORT}`)。 + 这个画面里就是服务器的浏览器本人,能看到你所有登录态 —— 只在局域网里用。 +
+当前平台: @@ -139,6 +141,12 @@ export function SettingsView({ onNavigate }: { onNavigate?: (view: AppView) => v