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.
+
+ )}
-
+
);
};