diff --git a/src/App.js b/src/App.js index 2e2aa26..36d0bd5 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; -import { Database, Globe, Download, Play, Pause, Trash2, Plus, RefreshCw, Activity, Clock, CircleX, CircleCheck } from 'lucide-react'; +import { Database, Globe, Download, Play, Pause, Trash2, Plus, RefreshCw, Activity, Clock, CircleX, CircleCheck, Grid, Calendar, Terminal } from 'lucide-react'; import * as signalR from '@microsoft/signalr'; const API_BASE = 'http://localhost:5123'; @@ -13,6 +13,9 @@ const WebScraper = () => { const [logs, setLogs] = useState([]); const [isConnected, setIsConnected] = useState(false); + // Estado de navegação simples para futuras páginas + const [activePage, setActivePage] = useState('scraper'); // 'dashboard' | 'scraper' | 'schedules' | 'logs' + const connectionRef = useRef(null); const addLog = (message, type = 'info') => { @@ -71,10 +74,7 @@ const WebScraper = () => { addLog('Conectado ao servidor', 'success'); connectionRef.current = connection; - // ✅ NOVO: assinar overview await connection.invoke('SubscribeOverview'); - - // pega snapshot inicial uma vez await loadSessions(); }) .catch(err => { @@ -111,7 +111,7 @@ const WebScraper = () => { const upsertSessionFromEvent = (sid, patch) => { setSessions(prev => { const idx = prev.findIndex(s => s.sessionId === sid); - if (idx === -1) return prev; // ou: return [...prev, {sessionId:sid, name:'', ...patch}] + if (idx === -1) return prev; const copy = [...prev]; copy[idx] = { ...copy[idx], ...patch }; return copy; @@ -150,7 +150,6 @@ const WebScraper = () => { case 'ItemStarted': addLog(`📄 Processando: ${event.url}`, 'info'); - // Atualiza runtime do painel se essa for a sessão selecionada setSessionStatus(prev => { if (!prev || prev.runtime?.sessionId !== sid) return prev; @@ -186,7 +185,6 @@ const WebScraper = () => { } }); - // também atualiza painel se for a sessão selecionada if (selectedSession?.sessionId === event.sessionId) { setSessionStatus(prev => ({ ...(prev ?? {}), @@ -307,7 +305,7 @@ const WebScraper = () => { setSelectedSessionId(session.sessionId); setLogs([]); addLog(`Sessão selecionada: ${session.name}`, 'info'); - loadSessionStatus(session.sessionId); // snapshot inicial + loadSessionStatus(session.sessionId); }; const formatNumber = (num) => { @@ -342,7 +340,7 @@ const WebScraper = () => { }; const handleSessionAction = (e, session) => { - e.stopPropagation(); // Evita selecionar a sessão ao clicar no botão + e.stopPropagation(); if (session.isRunning) { stopScrapingById(session.sessionId, session.name); } else { @@ -350,231 +348,303 @@ const WebScraper = () => { } }; - return ( -
-
- {/* Header */} -
-
-

- - Web Scraper Pro -

-

Gerenciador de scraping com API backend

-
-
-
- - {isConnected ? 'Conectado' : 'Desconectado'} -
- -
+ // Componente Header + const Header = () => ( +
+
+ +

Web Scraper Pro

+
+
+
+ + {isConnected ? 'Conectado' : 'Desconectado'}
+ +
+
+ ); -
- {/* Lista de Sessões */} -
-

- - Sessões -

-
- {sessions.map((session) => ( -
selectSession(session)} - className={`p-4 rounded-lg cursor-pointer transition ${selectedSession?.sessionId === session.sessionId - ? 'bg-blue-600' - : 'bg-slate-700 hover:bg-slate-600' - }`} - > -
- {session.name} -
- {session.isRunning && ( - - Rodando - - )} - -
-
-
-
Total: {formatNumber(session.queue?.total || 0)}
-
-  {formatNumber(session.queue?.done || 0)} -  {formatNumber(session.queue?.pending || 0)} -  {formatNumber(session.queue?.failed || 0)} -
-
-
- ))} - {sessions.length === 0 && ( -
- Nenhuma sessão disponível -
- )} + // Componente Sidebar + const Sidebar = () => ( + + ); + + return ( +
+
+ + + {/* Conteúdo principal */} +
+
+ {/* Breadcrumb e título da página ativa */} +
+
+ Extratores + / + {activePage === 'scraper' ? 'Scraper' : activePage.charAt(0).toUpperCase() + activePage.slice(1)}
-
- - {/* Detalhes e Controles */} -
- {selectedSession ? ( - <> - {/* Controles */} -
-

Controles - {selectedSession.name}

- -
- setNewUrl(e.target.value)} - placeholder="https://site.com/page" - className="flex-1 px-4 py-2 bg-slate-700 rounded-lg border border-slate-600 focus:border-blue-500 focus:outline-none" - onKeyPress={(e) => e.key === 'Enter' && addUrlToQueue()} - /> - -
- -
- {!selectedSession.isRunning ? ( - - ) : ( - - )} -
-
- - {/* Estatísticas */} - {sessionStatus && ( - <> -
-

Progresso

- - {/* Barra de Progresso */} -
-
- Processado - {getProgressPercent()}% -
-
-
-
-
- -
-
-
Total
-
{formatNumber(sessionStatus.counts?.total)}
-
-
-
Concluído
-
{formatNumber(sessionStatus.counts?.done)}
-
-
-
Pendente
-
{formatNumber(sessionStatus.counts?.pending)}
-
-
-
Processando
-
{formatNumber(sessionStatus.counts?.processing)}
-
-
-
Falhas
-
{formatNumber(sessionStatus.counts?.failed)}
-
-
- - {/* URL Atual */} - {sessionStatus.runtime?.isRunning && sessionStatus.runtime?.currentUrl && ( -
-
Processando agora:
-
- {sessionStatus.runtime.currentUrl} -
-
- Iniciado: {new Date(sessionStatus.runtime.currentStartedAt).toLocaleTimeString('pt-BR')} -
-
- )} -
- - )} - - {/* Logs */} -
-

Logs em Tempo Real

-
- {logs.map((log, idx) => ( -
- [{log.time}] {log.message} -
- ))} - {logs.length === 0 && ( -
- Nenhum evento ainda. Selecione uma sessão e inicie o scraping. -
- )} -
-
- - ) : ( -
- -

Selecione uma Sessão

-

- Escolha uma sessão à esquerda para começar +

+
+

+ {activePage === 'scraper' ? 'Gerenciador de Sessões' : 'Em breve'} +

+

+ {activePage === 'scraper' ? 'Gerencie e monitore suas sessões de scraping.' : 'Conteúdo a ser implementado.'}

- )} +
+ + {/* Render da página ativa */} + {activePage === 'scraper' ? ( +
+
+ {/* Lista de Sessões */} +
+

+ + Sessões +

+
+ {sessions.map((session) => ( +
selectSession(session)} + className={`p-4 rounded-lg cursor-pointer transition ${selectedSession?.sessionId === session.sessionId + ? 'bg-blue-600' + : 'bg-slate-700 hover:bg-slate-600' + }`} + > +
+ {session.name} +
+ {session.isRunning && ( + + Rodando + + )} + +
+
+
+
Total: {formatNumber(session.queue?.total || 0)}
+
+  {formatNumber(session.queue?.done || 0)} +  {formatNumber(session.queue?.pending || 0)} +  {formatNumber(session.queue?.failed || 0)} +
+
+
+ ))} + {sessions.length === 0 && ( +
+ Nenhuma sessão disponível +
+ )} +
+
+ + {/* Detalhes e Controles */} +
+ {selectedSession ? ( + <> + {/* Controles */} +
+

Controles - {selectedSession.name}

+ +
+ setNewUrl(e.target.value)} + placeholder="https://site.com/page" + className="flex-1 px-4 py-2 bg-slate-700 rounded-lg border border-slate-600 focus:border-blue-500 focus:outline-none" + onKeyPress={(e) => e.key === 'Enter' && addUrlToQueue()} + /> + +
+ +
+ {!selectedSession.isRunning ? ( + + ) : ( + + )} +
+
+ + {/* Estatísticas */} + {sessionStatus && ( + <> +
+

Progresso

+ + {/* Barra de Progresso */} +
+
+ Processado + {getProgressPercent()}% +
+
+
+
+
+ +
+
+
Total
+
{formatNumber(sessionStatus.counts?.total)}
+
+
+
Concluído
+
{formatNumber(sessionStatus.counts?.done)}
+
+
+
Pendente
+
{formatNumber(sessionStatus.counts?.pending)}
+
+
+
Processando
+
{formatNumber(sessionStatus.counts?.processing)}
+
+
+
Falhas
+
{formatNumber(sessionStatus.counts?.failed)}
+
+
+ + {/* URL Atual */} + {sessionStatus.runtime?.isRunning && sessionStatus.runtime?.currentUrl && ( +
+
Processando agora:
+
+ {sessionStatus.runtime.currentUrl} +
+
+ Iniciado: {new Date(sessionStatus.runtime.currentStartedAt).toLocaleTimeString('pt-BR')} +
+
+ )} +
+ + )} + + {/* Logs */} +
+

Logs em Tempo Real

+
+ {logs.map((log, idx) => ( +
+ [{log.time}] {log.message} +
+ ))} + {logs.length === 0 && ( +
+ Nenhum evento ainda. Selecione uma sessão e inicie o scraping. +
+ )} +
+
+ + ) : ( +
+ +

Selecione uma Sessão

+

+ Escolha uma sessão à esquerda para começar +

+
+ )} +
+
+
+ ) : ( +
+ Conteúdo da página "{activePage}" em breve. +
+ )}
-
+
); };