diff --git a/README.md b/README.md index 507295b..2f2a2f2 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,20 @@ Clean up smoke helper processes when you are done: The cleanup helper stops identifiable FBKit agent listeners on ports `8100` and `9222`, removes stale agent smoke PID files, and keeps Chrome open by default. Pass `-IncludeChrome` only if you started a dedicated smoke Chrome profile and want to close it too. +### 4. Pair with ZooPost Cloud in dry-run mode + +Create an agent installation from ZooPost Cloud **Kết nối Agent**, then exchange the one-time registration token locally. The helper prints PowerShell environment commands only; it does not write credential files. + +```powershell +$env:ZOOPOST_CLOUD_BEARER_TOKEN="" +.\.venv\Scripts\python.exe scripts\zoopost-agent-env-setup.py ` + --cloud-url http://127.0.0.1:8200 ` + --installation-id ` + --registration-token +``` + +Run the printed commands in the same terminal that starts FBKit. Pairing remains dry-run-only: keep `LIVE_ACTIONS_ENABLED=false`, `DRY_RUN_DEFAULT=true`, `APPROVAL_REQUIRED=true`, `API_AUTH_ENABLED=false`, and `WS_AUTH_ENABLED=false`. ZooPost Cloud must not receive cookies, browser profiles, Facebook credentials, or local media filesystem paths. + ## Safety Gate Defaults FBKit centralizes mutation safety in `agent/services/safety_gate.py`. diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index ba4d71a..0a814ea 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -20,6 +20,7 @@ import SpyPage from './pages/SpyPage' import TasksPage from './pages/TasksPage' import LogsPage from './pages/LogsPage' import AutoPostFanpagePage from './pages/AutoPostFanpagePage' +import AgentOnboardingPage from './pages/AgentOnboardingPage' const NAV = [ { to: '/', icon: LayoutDashboard, label: 'Tổng Quan', exact: true, element: }, @@ -31,7 +32,7 @@ const NAV = [ { to: '/comments', icon: MessageCircle, label: 'Bình luận tự động', exact: false, element: }, { to: '/inbox', icon: Inbox, label: 'Messenger/Inbox', exact: false, element: }, { to: '/reports', icon: BarChart3, label: 'Báo cáo', exact: false, element: }, - { to: '/settings', icon: Settings, label: 'Cài đặt', exact: false, element: }, + { to: '/settings', icon: Settings, label: 'Cài đặt', exact: false, element: }, { to: '/guide', icon: HelpCircle, label: 'Hướng dẫn', exact: false, element: }, ] diff --git a/dashboard/src/pages/AgentOnboardingPage.tsx b/dashboard/src/pages/AgentOnboardingPage.tsx new file mode 100644 index 0000000..7d514b0 --- /dev/null +++ b/dashboard/src/pages/AgentOnboardingPage.tsx @@ -0,0 +1,150 @@ +import { useCallback, useEffect, useMemo, useState } from 'react' +import type { CSSProperties, ReactNode } from 'react' +import { CheckCircle, Copy, PlugZap, RefreshCw, ShieldCheck, Terminal, XCircle } from 'lucide-react' +import { fetchAPI, postAPI } from '../api/client' +import type { AgentInstallation, AgentInstallationCreateResponse, AgentSessionReadiness } from '../types' + +export default function AgentOnboardingPage() { + const [installations, setInstallations] = useState([]) + const [sessions, setSessions] = useState>({}) + const [selectedId, setSelectedId] = useState('') + const [name, setName] = useState('FBKit Local Agent') + const [registrationToken, setRegistrationToken] = useState('') + const [message, setMessage] = useState(null) + const [loading, setLoading] = useState(false) + + const selected = installations.find(item => item.id === selectedId) ?? installations[0] ?? null + const selectedSessions = selected ? sessions[selected.id] ?? [] : [] + const readySessions = selectedSessions.filter(session => session.dry_run_ready) + const setupCommand = useMemo(() => { + if (!selected || !registrationToken.trim()) return '' + return `$env:ZOOPOST_CLOUD_BEARER_TOKEN=""\n.\\.venv\\Scripts\\python.exe scripts\\zoopost-agent-env-setup.py --cloud-url http://127.0.0.1:8200 --installation-id ${selected.id} --registration-token ${registrationToken.trim()}` + }, [registrationToken, selected]) + + const load = useCallback(async () => { + setLoading(true) + try { + const items = await fetchAPI('/api/agent-installations') + setInstallations(items) + const sessionPairs = await Promise.all(items.map(async item => [item.id, await fetchAPI(`/api/agent-installations/${item.id}/sessions`)] as const)) + setSessions(Object.fromEntries(sessionPairs)) + setMessage(null) + } catch (error) { + setMessage(error instanceof Error ? error.message : 'Không tải được trạng thái agent.') + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { load() }, [load]) + + async function createInstallation() { + if (!name.trim()) { + setMessage('Nhập tên agent trước khi tạo kết nối.') + return + } + setLoading(true) + try { + const created = await postAPI('/api/agent-installations', { name }) + setRegistrationToken(created.registration_token) + setSelectedId(created.id) + setInstallations(prev => [created, ...prev.filter(item => item.id !== created.id)]) + setMessage('Đã tạo token đăng ký. Copy token này sang máy chạy FBKit local trong 15 phút.') + } catch (error) { + setMessage(error instanceof Error ? error.message : 'Không tạo được agent installation.') + } finally { + setLoading(false) + } + } + + async function copySetup() { + if (!setupCommand) return + await navigator.clipboard.writeText(setupCommand) + setMessage('Đã copy lệnh local helper. Exchange credential chỉ chạy trên máy local.') + } + + return ( +
+
+
+
Kết nối Agent
+
Pair ZooPost Cloud với FBKit local ở chế độ dry-run. Cloud không lưu cookie, profile trình duyệt hoặc credential Facebook.
+
+ +
+ + {message &&
{message}
} + +
+
Safety boundary
+
+ {['LIVE_ACTIONS_ENABLED=false', 'DRY_RUN_DEFAULT=true', 'APPROVAL_REQUIRED=true', 'API_AUTH_ENABLED=false', 'WS_AUTH_ENABLED=false', 'Không có live arm / approval / real posting'].map(item =>
{item}
)} +
+
+ +
+
+ } title="Bước 1 — Tạo installation" /> + setName(event.target.value)} placeholder="Tên agent" style={inputStyle()} /> + + + + + + {selected &&
+
ID: {selected.id}
+
Token generation: {selected.token_generation}
+
Credential last used: {selected.credential_last_used_at ?? 'chưa dùng'}
+
} +
+ +
+ } title="Bước 2 — Chạy exchange trên máy local" /> +