Files
MediaCrawler/webui/src/components/monitor/CookiePanel.tsx
T
butubb 2f5852e311
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
fix(ui): 运营与扫码登录态没跟着平台走,且两个登录面板分不清
两个 bug 是同一类问题:没做平台门禁。

1) 切到抖音等平台,「运营」仍列出小红书账号。运营读的是小红书创作者后台,
   其它平台根本没有对应的后台接口。现在按平台门禁换掉整个视图并说明原因 ——
   与 MonitorDashboard / ReportView 用 isWired 的做法一致。

2) 扫码登录面板把小红书的会话当成任意平台的登录态报。它的检测读的是小红书页面的
   __INITIAL_STATE__,后端 qrlogin.LOGIN_URL 里也只有 xhs 一项;而标题写的是
   「{当前平台}登录态」,于是切到抖音照样显示已登录—— 这是个具体的谎。
   现在非小红书直接换掉整个面板(只改标题不够,下面的块读的仍是小红书的状态)。

3) 两个面板的标题都是「登录态」,看不出区别。它们其实是不同机制:
   - Cookie 面板:存进库、每轮以 --cookies_file 注入子进程 → 改名为「Cookie(定时任务用)」
   - 扫码面板:写进浏览器 profile、CDP 模式复用          → 改名为「浏览器登录态(扫码)」
2026-10-07 16:35:12 +08:00

137 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { AlertTriangle, CheckCircle2, KeyRound, Trash2 } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { useClearCookie, useCookieStatus, useSetCookie } from '@/hooks/useMonitor'
import { useCurrentPlatform } from '@/hooks/usePlatform'
import { formatDateTime, formatRelative } from '@/lib/monitorFormat'
/**
* Cookie health for unattended runs.
*
* The expiry signal that matters is `last_ok_at` (the last run that actually
* authenticated), not `updated_at`: a cookie can be saved long ago and still be
* working, or be fresh and already rejected.
*/
export function CookiePanel() {
const { capability, platform } = useCurrentPlatform()
// Scoped to the selected platform: each keeps its own login.
const { data: status } = useCookieStatus()
const setCookie = useSetCookie()
const clearCookie = useClearCookie()
const [draft, setDraft] = useState('')
const [editing, setEditing] = useState(false)
const showForm = !status?.present || editing
const label = capability?.label ?? platform
return (
<div className="rounded-lg glass-panel float-panel p-4 space-y-3">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<KeyRound className="w-4 h-4 text-cyber-neon-cyan" />
{/* 标题要和扫码面板区分开:这个是"存进库里、每轮注入子进程"的 cookie,
那个是"写进浏览器 profile、CDP 模式复用"的登录态。两者是不同机制、
也都能同时算"登录着",都叫「登录态」就分不清在说哪一个。 */}
<span className="font-mono text-xs tracking-wider text-cyber-text-primary">
{label} Cookie(定时任务用)
</span>
{status?.present ? (
<Badge variant="success" className="text-[10px]">
已配置
</Badge>
) : (
<Badge variant="warning" className="text-[10px]">
未配置
</Badge>
)}
</div>
<div className="flex items-center gap-2">
{status?.present && !editing && (
<>
<Button variant="outline" size="sm" onClick={() => setEditing(true)}>
替换
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => clearCookie.mutate()}
disabled={clearCookie.isPending}
>
<Trash2 className="w-3 h-3" />
</Button>
</>
)}
</div>
</div>
{status?.present && !editing && (
<div className="flex flex-wrap items-center gap-x-6 gap-y-1 text-[11px] font-mono text-cyber-text-secondary">
<span>
长度 <span className="text-cyber-neon-cyan">{status.length}</span>
</span>
<span>
保存于 <span className="text-cyber-text-primary">{formatDateTime(status.updated_at)}</span>
</span>
<span className="flex items-center gap-1">
最近成功
{status.last_ok_at ? (
<>
<CheckCircle2 className="w-3 h-3 text-cyber-neon-green" />
<span className="text-cyber-neon-green">{formatRelative(status.last_ok_at)}</span>
</>
) : (
<>
<AlertTriangle className="w-3 h-3 text-cyber-neon-orange" />
<span className="text-cyber-neon-orange">尚无成功记录</span>
</>
)}
</span>
</div>
)}
{showForm && (
<div className="space-y-2">
<p className="text-[11px] font-mono text-cyber-text-muted leading-relaxed">
定时监控需要长期有效的登录态。在浏览器登录{label}后,从开发者工具复制完整 Cookie
粘贴到此处(需包含 <span className="text-cyber-neon-cyan">web_session</span>、
<span className="text-cyber-neon-cyan">a1</span>、
<span className="text-cyber-neon-cyan">webId</span>)。
Cookie 通常数周后失效,届时任务会提示重新粘贴。该 Cookie 只用于{label}。
</p>
<textarea
value={draft}
onChange={(event) => setDraft(event.target.value)}
placeholder="web_session=xxxx; a1=xxxx; webId=xxxx; ..."
rows={4}
className="w-full rounded-md border border-cyber-border-DEFAULT bg-cyber-bg-tertiary p-2 font-mono text-[11px] text-cyber-text-primary outline-none focus-visible:border-cyber-neon-cyan/50"
/>
<div className="flex items-center gap-2">
<Button
size="sm"
disabled={!draft.trim() || setCookie.isPending}
onClick={() =>
setCookie.mutate(draft.trim(), {
onSuccess: () => {
setDraft('')
setEditing(false)
},
})
}
>
保存
</Button>
{status?.present && (
<Button variant="ghost" size="sm" onClick={() => setEditing(false)}>
取消
</Button>
)}
</div>
</div>
)}
</div>
)
}