| <!DOCTYPE html> |
| <html lang="pt-BR"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Painel de Controle - DeekSeek</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> |
| <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> |
| <style> |
| :root { |
| --bg-primary: #1a1a2e; |
| --bg-secondary: #16213e; |
| --text-primary: #e6e6e6; |
| --text-secondary: #b8b8b8; |
| --accent: #4f46e5; |
| --transition-speed: 0.3s; |
| } |
| |
| .light { |
| --bg-primary: #f8fafc; |
| --bg-secondary: #ffffff; |
| --text-primary: #1e293b; |
| --text-secondary: #64748b; |
| } |
| |
| body { |
| background-color: var(--bg-primary); |
| color: var(--text-primary); |
| transition: all var(--transition-speed) ease; |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; |
| } |
| |
| .sidebar { |
| background-color: var(--bg-secondary); |
| border-right: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .card { |
| background-color: var(--bg-secondary); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| transition: transform 0.2s ease, box-shadow 0.2s ease; |
| border-radius: 0.5rem; |
| } |
| |
| .card:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2); |
| } |
| |
| .loading-spinner { |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| @media (max-width: 768px) { |
| .sidebar { |
| width: 100%; |
| position: fixed; |
| z-index: 40; |
| height: auto; |
| bottom: 0; |
| left: 0; |
| right: 0; |
| border-right: none; |
| border-top: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .sidebar-nav { |
| display: flex; |
| overflow-x: auto; |
| } |
| |
| .sidebar-nav ul { |
| display: flex; |
| flex-direction: row; |
| } |
| |
| .sidebar-nav li { |
| margin-right: 0.5rem; |
| } |
| |
| .sidebar-nav button { |
| padding: 0.5rem; |
| white-space: nowrap; |
| } |
| |
| .sidebar-nav i { |
| margin-right: 0; |
| } |
| |
| .sidebar-nav span { |
| display: none; |
| } |
| |
| .user-profile { |
| display: none; |
| } |
| |
| main { |
| padding-bottom: 80px; |
| } |
| } |
| </style> |
| </head> |
| <body class="dark"> |
| <div id="root"></div> |
|
|
| <script> |
| // Configuração da API |
| const API_BASE_URL = 'http://localhost:3000/api'; |
| |
| // Serviços de API |
| const ApiService = { |
| async getNews() { |
| try { |
| const response = await axios.get(`${API_BASE_URL}/news`); |
| return response.data; |
| } catch (error) { |
| console.error('Erro ao buscar notícias:', error); |
| return []; |
| } |
| }, |
| |
| async getAlerts() { |
| try { |
| const response = await axios.get(`${API_BASE_URL}/alerts`); |
| return response.data; |
| } catch (error) { |
| console.error('Erro ao buscar alertas:', error); |
| return []; |
| } |
| }, |
| |
| async postReport(reportData) { |
| try { |
| const response = await axios.post(`${API_BASE_URL}/denuncias`, reportData); |
| return response.data; |
| } catch (error) { |
| console.error('Erro ao enviar denúncia:', error); |
| throw error; |
| } |
| }, |
| |
| async getDashboardData() { |
| try { |
| const response = await axios.get(`${API_BASE_URL}/dashboard`); |
| return response.data; |
| } catch (error) { |
| console.error('Erro ao buscar dados do dashboard:', error); |
| return null; |
| } |
| } |
| }; |
| |
| // Componentes reutilizáveis |
| const Button = ({ children, icon, onClick, variant = 'primary', size = 'md', className = '', disabled = false }) => { |
| const baseClasses = 'flex items-center justify-center rounded-lg font-medium transition-all'; |
| const sizeClasses = { |
| sm: 'px-3 py-1 text-sm', |
| md: 'px-4 py-2', |
| lg: 'px-6 py-3' |
| }; |
| const variantClasses = { |
| primary: 'bg-indigo-600 hover:bg-indigo-700 text-white', |
| secondary: 'bg-gray-700 hover:bg-gray-600 text-white', |
| success: 'bg-green-600 hover:bg-green-700 text-white', |
| danger: 'bg-red-600 hover:bg-red-700 text-white', |
| warning: 'bg-yellow-600 hover:bg-yellow-700 text-white', |
| ghost: 'bg-transparent hover:bg-gray-700/50 text-white' |
| }; |
| |
| return ` |
| <button |
| onclick="${onClick}" |
| ${disabled ? 'disabled' : ''} |
| class="${baseClasses} ${sizeClasses[size]} ${variantClasses[variant]} ${className}" |
| > |
| ${icon ? `<i class="${icon} ${children ? 'mr-2' : ''}"></i>` : ''} |
| ${children || ''} |
| </button> |
| `; |
| }; |
| |
| const Card = ({ children, title, className = '', headerClassName = '', bodyClassName = '' }) => { |
| return ` |
| <div class="card ${className}"> |
| ${title ? ` |
| <div class="border-b border-gray-700 p-4 ${headerClassName}"> |
| <h3 class="font-semibold">${title}</h3> |
| </div> |
| ` : ''} |
| <div class="p-4 ${bodyClassName}"> |
| ${children} |
| </div> |
| </div> |
| `; |
| }; |
| |
| const LoadingSpinner = () => { |
| return ` |
| <div class="flex items-center justify-center h-64"> |
| <i class="fas fa-circle-notch fa-spin text-4xl text-indigo-500 loading-spinner"></i> |
| </div> |
| `; |
| }; |
| |
| // Componentes principais |
| const App = () => { |
| const [darkMode, setDarkMode] = useState(true); |
| const [activeSection, setActiveSection] = useState('dashboard'); |
| const [isLoading, setIsLoading] = useState(true); |
| const [dashboardData, setDashboardData] = useState(null); |
| const [news, setNews] = useState([]); |
| const [alerts, setAlerts] = useState([]); |
| |
| const toggleDarkMode = () => { |
| setDarkMode(prev => { |
| const newMode = !prev; |
| document.body.classList.toggle('dark'); |
| document.body.classList.toggle('light'); |
| return newMode; |
| }); |
| }; |
| |
| const fetchData = async () => { |
| setIsLoading(true); |
| try { |
| const [dashboard, newsData, alertsData] = await Promise.all([ |
| ApiService.getDashboardData(), |
| ApiService.getNews(), |
| ApiService.getAlerts() |
| ]); |
| |
| setDashboardData(dashboard); |
| setNews(newsData); |
| setAlerts(alertsData); |
| } catch (error) { |
| console.error('Erro ao carregar dados:', error); |
| } finally { |
| setIsLoading(false); |
| } |
| }; |
| |
| useEffect(() => { |
| fetchData(); |
| }, []); |
| |
| const renderSection = () => { |
| if (isLoading) return LoadingSpinner(); |
| |
| switch (activeSection) { |
| case 'dashboard': |
| return DashboardSection({ dashboardData, news, alerts }); |
| case 'news': |
| return NewsSection({ news }); |
| case 'alerts': |
| return AlertsSection({ alerts }); |
| case 'reports': |
| return ReportsSection(); |
| default: |
| return DashboardSection({ dashboardData, news, alerts }); |
| } |
| }; |
| |
| return ` |
| <div class="flex h-screen overflow-hidden"> |
| |
| ${Sidebar({ activeSection, setActiveSection })} |
| |
| |
| <div class="flex-1 overflow-auto"> |
| |
| ${Header({ |
| activeSection, |
| darkMode, |
| toggleDarkMode, |
| isLoading |
| })} |
| |
| |
| <main class="p-4 md:p-6"> |
| ${renderSection()} |
| </main> |
| </div> |
| </div> |
| `; |
| }; |
| |
| const Sidebar = ({ activeSection, setActiveSection }) => { |
| const sections = [ |
| { id: 'dashboard', icon: 'fa-tachometer-alt', label: 'Dashboard' }, |
| { id: 'news', icon: 'fa-newspaper', label: 'Notícias' }, |
| { id: 'alerts', icon: 'fa-bell', label: 'Alertas' }, |
| { id: 'reports', icon: 'fa-flag', label: 'Denúncias' } |
| ]; |
| |
| return ` |
| <div class="sidebar w-16 md:w-64 flex-shrink-0 flex flex-col"> |
| <div class="p-4 border-b border-gray-700 flex items-center justify-center md:justify-between"> |
| <h1 class="text-xl font-bold text-white flex items-center"> |
| <i class="fas fa-search text-indigo-500 mr-0 md:mr-2"></i> |
| <span class="hidden md:inline">DeekSeek</span> |
| </h1> |
| </div> |
| |
| <div class="flex-1 overflow-y-auto"> |
| <nav class="p-2 md:p-4 sidebar-nav"> |
| <ul class="space-y-1 md:space-y-2"> |
| ${sections.map(section => ` |
| <li key="${section.id}" class="mb-0 md:mb-2"> |
| ${Button({ |
| onClick: `(${setActiveSection})('${section.id}')`, |
| variant: activeSection === section.id ? 'primary' : 'ghost', |
| className: `w-full text-left ${activeSection === section.id ? 'bg-indigo-500/20 text-indigo-400' : 'hover:bg-gray-700'}`, |
| icon: section.icon, |
| children: `<span class="hidden md:inline">${section.label}</span>` |
| })} |
| </li> |
| `).join('')} |
| </ul> |
| </nav> |
| </div> |
| |
| <div class="p-4 border-t border-gray-700 user-profile"> |
| <div class="flex items-center justify-center md:justify-between"> |
| <div class="flex items-center"> |
| <div class="w-10 h-10 rounded-full bg-indigo-500 flex items-center justify-center"> |
| <i class="fas fa-user text-white"></i> |
| </div> |
| <div class="ml-3 hidden md:block"> |
| <p class="text-sm font-medium">Usuário</p> |
| <p class="text-xs text-gray-400">Admin</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| `; |
| }; |
| |
| const Header = ({ activeSection, darkMode, toggleDarkMode, isLoading }) => { |
| const sectionTitles = { |
| dashboard: 'Dashboard', |
| news: 'Gerenciamento de Notícias', |
| alerts: 'Monitoramento de Alertas', |
| reports: 'Registro de Denúncias' |
| }; |
| |
| return ` |
| <header class="bg-gray-800 border-b border-gray-700 p-4 flex flex-col md:flex-row justify-between items-start md:items-center space-y-3 md:space-y-0"> |
| <h2 class="text-xl font-semibold">${sectionTitles[activeSection]}</h2> |
| |
| <div class="flex items-center space-x-2 md:space-x-4 w-full md:w-auto"> |
| <div class="relative flex-1 md:flex-none"> |
| <i class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i> |
| <input |
| type="text" |
| placeholder="Pesquisar..." |
| class="bg-gray-700 border border-gray-600 rounded-lg pl-10 pr-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 w-full md:w-64" |
| ${isLoading ? 'disabled' : ''} |
| /> |
| </div> |
| |
| ${Button({ |
| icon: darkMode ? 'fa-sun' : 'fa-moon', |
| variant: 'ghost', |
| onClick: `(${toggleDarkMode})()`, |
| size: 'sm' |
| })} |
| </div> |
| </header> |
| `; |
| }; |
| |
| // Seções |
| const DashboardSection = ({ dashboardData, news, alerts }) => { |
| if (!dashboardData) return LoadingSpinner(); |
| |
| return ` |
| <div class="space-y-6"> |
| |
| <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"> |
| ${Card({ |
| className: 'relative', |
| children: ` |
| <div class="flex justify-between items-start"> |
| <div> |
| <p class="text-gray-400">Notícias</p> |
| <h3 class="text-2xl font-bold mt-1">${news.length}</h3> |
| </div> |
| <div class="bg-blue-500/10 p-3 rounded-full"> |
| <i class="fas fa-newspaper text-blue-500"></i> |
| </div> |
| </div> |
| ` |
| })} |
| |
| ${Card({ |
| className: 'relative', |
| children: ` |
| <div class="flex justify-between items-start"> |
| <div> |
| <p class="text-gray-400">Alertas</p> |
| <h3 class="text-2xl font-bold mt-1">${alerts.length}</h3> |
| </div> |
| <div class="bg-red-500/10 p-3 rounded-full"> |
| <i class="fas fa-bell text-red-500"></i> |
| </div> |
| </div> |
| ` |
| })} |
| |
| ${Card({ |
| className: 'relative', |
| children: ` |
| <div class="flex justify-between items-start"> |
| <div> |
| <p class="text-gray-400">Última Atualização</p> |
| <h3 class="text-2xl font-bold mt-1">${new Date(dashboardData.lastUpdated).toLocaleTimeString()}</h3> |
| </div> |
| <div class="bg-green-500/10 p-3 rounded-full"> |
| <i class="fas fa-clock text-green-500"></i> |
| </div> |
| </div> |
| ` |
| })} |
| |
| ${Card({ |
| className: 'relative', |
| children: ` |
| <div class="flex justify-between items-start"> |
| <div> |
| <p class="text-gray-400">Status do Sistema</p> |
| <h3 class="text-2xl font-bold mt-1">${dashboardData.systemStatus}</h3> |
| </div> |
| <div class="bg-yellow-500/10 p-3 rounded-full"> |
| <i class="fas fa-server text-yellow-500"></i> |
| </div> |
| </div> |
| ` |
| })} |
| </div> |
| |
| |
| ${Card({ |
| title: 'Atividade Recente', |
| children: ` |
| <div class="space-y-4"> |
| ${alerts.slice(0, 5).map(alert => ` |
| <div class="flex items-start"> |
| <div class="p-2 rounded-full mr-3 bg-red-500/10 text-red-500"> |
| <i class="fas fa-exclamation-triangle"></i> |
| </div> |
| <div> |
| <p class="text-sm">${alert.message}</p> |
| <p class="text-xs text-gray-400">${new Date(alert.date).toLocaleString()}</p> |
| </div> |
| </div> |
| `).join('')} |
| </div> |
| ` |
| })} |
| </div> |
| `; |
| }; |
| |
| const NewsSection = ({ news }) => { |
| const [selectedNews, setSelectedNews] = useState(null); |
| |
| return ` |
| <div class="space-y-6"> |
| <div class="flex justify-between items-center"> |
| <h3 class="text-lg font-semibold">Gerenciamento de Notícias</h3> |
| ${Button({ |
| onClick: "alert('Funcionalidade de adicionar notícia será implementada')", |
| variant: 'primary', |
| icon: 'fa-plus', |
| children: 'Adicionar Notícia' |
| })} |
| </div> |
| |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| |
| <div class="lg:col-span-1"> |
| ${Card({ |
| title: 'Lista de Notícias', |
| children: ` |
| <div class="space-y-3"> |
| ${news.map(item => ` |
| <div |
| onclick="(${setSelectedNews})(${JSON.stringify(item)})" |
| class="p-3 rounded-lg cursor-pointer transition-all ${ |
| selectedNews?.id === item.id ? 'bg-indigo-500/10 border border-indigo-500/30' : 'hover:bg-gray-700' |
| }" |
| > |
| <div class="flex justify-between items-center"> |
| <h4 class="font-medium truncate">${item.title}</h4> |
| <span class="px-2 py-1 text-xs rounded-full bg-green-500/20 text-green-400"> |
| ${item.status} |
| </span> |
| </div> |
| <p class="text-sm text-gray-400 truncate">${item.source}</p> |
| <div class="flex justify-between mt-2 text-xs"> |
| <span class="text-gray-400">Publicado em: ${new Date(item.publishedDate).toLocaleDateString()}</span> |
| </div> |
| </div> |
| `).join('')} |
| </div> |
| ` |
| })} |
| </div> |
| |
| |
| <div class="lg:col-span-2"> |
| ${selectedNews ? ` |
| ${Card({ |
| children: ` |
| <div class="flex flex-col md:flex-row md:justify-between md:items-center mb-4 space-y-3 md:space-y-0"> |
| <h3 class="text-xl font-semibold">${selectedNews.title}</h3> |
| <div class="flex space-x-2"> |
| ${Button({ |
| variant: 'primary', |
| size: 'sm', |
| icon: 'fa-edit', |
| children: 'Editar' |
| })} |
| ${Button({ |
| variant: 'danger', |
| size: 'sm', |
| icon: 'fa-trash', |
| children: 'Excluir' |
| })} |
| </div> |
| </div> |
| |
| <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6"> |
| ${Card({ |
| children: ` |
| <p class="text-gray-400 text-sm">Fonte</p> |
| <p class="font-medium">${selectedNews.source}</p> |
| ` |
| })} |
| ${Card({ |
| children: ` |
| <p class="text-gray-400 text-sm">Status</p> |
| <span class="px-2 py-1 text-xs rounded-full bg-green-500/20 text-green-400"> |
| ${selectedNews.status} |
| </span> |
| ` |
| })} |
| ${Card({ |
| children: ` |
| <p class="text-gray-400 text-sm">Data de Publicação</p> |
| <p class="font-medium">${new Date(selectedNews.publishedDate).toLocaleString()}</p> |
| ` |
| })} |
| ${Card({ |
| children: ` |
| <p class="text-gray-400 text-sm">Autor</p> |
| <p class="font-medium">${selectedNews.author}</p> |
| ` |
| })} |
| </div> |
| |
| <div> |
| <h4 class="font-medium mb-3">Conteúdo</h4> |
| <div class="bg-gray-800 border border-gray-700 rounded-lg p-4"> |
| <p class="text-gray-300">${selectedNews.content}</p> |
| </div> |
| </div> |
| ` |
| })} |
| ` : ` |
| ${Card({ |
| className: 'flex flex-col items-center justify-center h-64', |
| children: ` |
| <i class="fas fa-newspaper text-4xl text-gray-500 mb-4"></i> |
| <p class="text-gray-400">Selecione uma notícia para visualizar os detalhes</p> |
| ` |
| })} |
| `} |
| </div> |
| </div> |
| </div> |
| `; |
| }; |
| |
| const AlertsSection = ({ alerts }) => { |
| const [selectedAlert, setSelectedAlert] = useState(null); |
| |
| return ` |
| <div class="space-y-6"> |
| <div class="flex justify-between items-center"> |
| <h3 class="text-lg font-semibold">Monitoramento de Alertas</h3> |
| ${Button({ |
| onClick: "alert('Funcionalidade de configuração de alertas será implementada')", |
| variant: 'primary', |
| icon: 'fa-cog', |
| children: 'Configurar' |
| })} |
| </div> |
| |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| |
| <div class="lg:col-span-1"> |
| ${Card({ |
| title: 'Lista de Alertas', |
| children: ` |
| <div class="space-y-3"> |
| ${alerts.map(alert => ` |
| <div |
| onclick="(${setSelectedAlert})(${JSON.stringify(alert)})" |
| class="p-3 rounded-lg cursor-pointer transition-all ${ |
| selectedAlert?.id === alert.id ? 'bg-indigo-500/10 border border-indigo-500/30' : 'hover:bg-gray-700' |
| }" |
| > |
| <div class="flex justify-between items-center"> |
| <h4 class="font-medium truncate">${alert.title}</h4> |
| <span class="px-2 py-1 text-xs rounded-full bg-red-500/20 text-red-400"> |
| ${alert.severity} |
| </span> |
| </div> |
| <p class="text-sm text-gray-400 truncate">${alert.source}</p> |
| <div class="flex justify-between mt-2 text-xs"> |
| <span class="text-gray-400">Detectado em: ${new Date(alert.detectedAt).toLocaleTimeString()}</span> |
| </div> |
| </div> |
| `).join('')} |
| </div> |
| ` |
| })} |
| </div> |
| |
| |
| <div class="lg:col-span-2"> |
| ${selectedAlert ? ` |
| ${Card({ |
| children: ` |
| <div class="flex flex-col md:flex-row md:justify-between md:items-center mb-4 space-y-3 md:space-y-0"> |
| <h3 class="text-xl font-semibold">${selectedAlert.title}</h3> |
| <div class="flex space-x-2"> |
| ${Button({ |
| variant: 'primary', |
| size: 'sm', |
| icon: 'fa-check', |
| children: 'Resolver' |
| })} |
| ${Button({ |
| variant: 'danger', |
| size: 'sm', |
| icon: 'fa-trash', |
| children: 'Ignorar' |
| })} |
| </div> |
| </div> |
| |
| <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6"> |
| ${Card({ |
| children: ` |
| <p class="text-gray-400 text-sm">Fonte</p> |
| <p class="font-medium">${selectedAlert.source}</p> |
| ` |
| })} |
| ${Card({ |
| children: ` |
| <p class="text-gray-400 text-sm">Gravidade</p> |
| <span class="px-2 py-1 text-xs rounded-full bg-red-500/20 text-red-400"> |
| ${selectedAlert.severity} |
| </span> |
| ` |
| })} |
| ${Card({ |
| children: ` |
| <p class="text-gray-400 text-sm">Detectado em</p> |
| <p class="font-medium">${new Date(selectedAlert.detectedAt).toLocaleString()}</p> |
| ` |
| })} |
| ${Card({ |
| children: ` |
| <p class="text-gray-400 text-sm">Status</p> |
| <span class="px-2 py-1 text-xs rounded-full bg-yellow-500/20 text-yellow-400"> |
| ${selectedAlert.status} |
| </span> |
| ` |
| })} |
| </div> |
| |
| <div> |
| <h4 class="font-medium mb-3">Detalhes</h4> |
| <div class="bg-gray-800 border border-gray-700 rounded-lg p-4"> |
| <p class="text-gray-300">${selectedAlert.details}</p> |
| </div> |
| </div> |
| ` |
| })} |
| ` : ` |
| ${Card({ |
| className: 'flex flex-col items-center justify-center h-64', |
| children: ` |
| <i class="fas fa-bell text-4xl text-gray-500 mb-4"></i> |
| <p class="text-gray-400">Selecione um alerta para visualizar os detalhes</p> |
| ` |
| })} |
| `} |
| </div> |
| </div> |
| </div> |
| `; |
| }; |
| |
| const ReportsSection = () => { |
| const [reportData, setReportData] = useState({ |
| title: '', |
| description: '', |
| category: 'spam', |
| urgency: 'medium' |
| }); |
| const [isSubmitting, setIsSubmitting] = useState(false); |
| |
| const handleSubmit = async (e) => { |
| e.preventDefault(); |
| setIsSubmitting(true); |
| |
| try { |
| await ApiService.postReport(reportData); |
| alert('Denúncia enviada com sucesso!'); |
| setReportData({ |
| title: '', |
| description: '', |
| category: 'spam', |
| urgency: 'medium' |
| }); |
| } catch (error) { |
| alert('Erro ao enviar denúncia. Por favor, tente novamente.'); |
| } finally { |
| setIsSubmitting(false); |
| } |
| }; |
| |
| return ` |
| <div class="space-y-6"> |
| <div class="flex justify-between items-center"> |
| <h3 class="text-lg font-semibold">Registro de Denúncias</h3> |
| </div> |
| |
| ${Card({ |
| children: ` |
| <form onsubmit="event.preventDefault(); (${handleSubmit})(event);"> |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-400 mb-1">Título</label> |
| <input |
| type="text" |
| value="${reportData.title}" |
| onchange="(${setReportData})({...${JSON.stringify(reportData)}, title: event.target.value})" |
| placeholder="Resumo da denúncia" |
| class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" |
| required |
| /> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-400 mb-1">Descrição</label> |
| <textarea |
| value="${reportData.description}" |
| onchange="(${setReportData})({...${JSON.stringify(reportData)}, description: event.target.value})" |
| placeholder="Descreva detalhadamente o problema encontrado" |
| class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 h-32" |
| required |
| ></textarea> |
| </div> |
| |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-400 mb-1">Categoria</label> |
| <select |
| value="${reportData.category}" |
| onchange="(${setReportData})({...${JSON.stringify(reportData)}, category: event.target.value})" |
| class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" |
| > |
| <option value="spam">Spam</option> |
| <option value="fake_news">Fake News</option> |
| <option value="inappropriate">Conteúdo Inapropriado</option> |
| <option value="other">Outro</option> |
| </select> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-400 mb-1">Urgência</label> |
| <select |
| value="${reportData.urgency}" |
| onchange="(${setReportData})({...${JSON.stringify(reportData)}, urgency: event.target.value})" |
| class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" |
| > |
| <option value="low">Baixa</option> |
| <option value="medium">Média</option> |
| <option value="high">Alta</option> |
| </select> |
| </div> |
| </div> |
| |
| <div class="flex justify-end pt-4"> |
| ${Button({ |
| type: 'submit', |
| variant: 'primary', |
| disabled: isSubmitting, |
| children: isSubmitting ? ` |
| <i class="fas fa-circle-notch fa-spin mr-2 loading-spinner"></i> |
| Enviando... |
| ` : 'Enviar Denúncia' |
| })} |
| </div> |
| </div> |
| </form> |
| ` |
| })} |
| </div> |
| `; |
| }; |
| |
| // Funções auxiliares |
| function useState(initialValue) { |
| let state = initialValue; |
| const listeners = []; |
| |
| function setState(newValue) { |
| state = typeof newValue === 'function' ? newValue(state) : newValue; |
| listeners.forEach(listener => listener(state)); |
| } |
| |
| function useEffect(callback, deps) { |
| callback(); |
| } |
| |
| function render() { |
| document.getElementById('root').innerHTML = App(); |
| } |
| |
| listeners.push(render); |
| |
| return [state, setState, useEffect]; |
| } |
| |
| // Inicialização |
| const [darkMode, setDarkMode, useEffect] = useState(true); |
| document.getElementById('root').innerHTML = App(); |
| </script> |
|
|
| |
| <script> |
| |
| |
| |
| const serverCode = ` |
| const express = require('express'); |
| const cors = require('cors'); |
| const bodyParser = require('body-parser'); |
| |
| const app = express(); |
| const PORT = 3000; |
| |
| // Middleware |
| app.use(cors()); |
| app.use(bodyParser.json()); |
| |
| // Dados mockados |
| const news = [ |
| { |
| id: 1, |
| title: "Nova atualização do sistema", |
| source: "Portal Tech", |
| status: "Ativo", |
| publishedDate: "2023-05-15T10:30:00", |
| author: "João Silva", |
| content: "A nova atualização do sistema traz melhorias significativas na performance e segurança. Todos os usuários devem atualizar assim que possível." |
| }, |
| { |
| id: 2, |
| title: "Alerta de segurança", |
| source: "Segurança Digital", |
| status: "Crítico", |
| publishedDate: "2023-05-10T08:15:00", |
| author: "Maria Souza", |
| content: "Foi identificada uma vulnerabilidade crítica no sistema. Recomenda-se a aplicação imediata do patch de segurança." |
| }, |
| { |
| id: 3, |
| title: "Manutenção programada", |
| source: "TI Corporativa", |
| status: "Aviso", |
| publishedDate: "2023-05-05T14:00:00", |
| author: "Carlos Oliveira", |
| content: "Será realizada uma manutenção programada no próximo sábado entre 00:00 e 04:00. O sistema ficará indisponível durante este período." |
| }, |
| { |
| id: 4, |
| title: "Novo recurso disponível", |
| source: "Desenvolvimento", |
| status: "Ativo", |
| publishedDate: "2023-04-28T16:45:00", |
| author: "Ana Santos", |
| content: "O novo módulo de relatórios está disponível para todos os usuários. Consulte a documentação para mais detalhes." |
| }, |
| { |
| id: 5, |
| title: "Treinamento de usuários", |
| source: "RH Corporativo", |
| status: "Informação", |
| publishedDate: "2023-04-20T09:00:00", |
| author: "Pedro Rocha", |
| content: "Serão realizados treinamentos sobre as novas funcionalidades do sistema nos dias 25 e 26 deste mês." |
| } |
| ]; |
| |
| const alerts = [ |
| { |
| id: 1, |
| title: "Tentativa de acesso não autorizado", |
| source: "Sistema de Segurança", |
| severity: "Alta", |
| detectedAt: "2023-05-16T03:25:00", |
| status: "Não resolvido", |
| details: "Múltiplas tentativas de login falhas detectadas na conta de administrador. Possível ataque de força bruta." |
| }, |
| { |
| id: 2, |
| title: "Alta carga no servidor", |
| source: "Monitoramento", |
| severity: "Média", |
| detectedAt: "2023-05-15T14:30:00", |
| status: "Em análise", |
| details: "O servidor principal está operando com 95% de utilização de CPU. Recomenda-se investigação imediata." |
| }, |
| { |
| id: 3, |
| title: "Backup falhou", |
| source: "Sistema de Backup", |
| severity: "Crítica", |
| detectedAt: "2023-05-15T02:00:00", |
| status: "Não resolvido", |
| details: "O backup automático noturno falhou devido a espaço insuficiente em disco. Ação necessária." |
| }, |
| { |
| id: 4, |
| title: "Atualização pendente", |
| source: "Gerenciador de Pacotes", |
| severity: "Baixa", |
| detectedAt: "2023-05-14T09:15:00", |
| status: "Pendente", |
| details: "Existem 5 atualizações de segurança pendentes para aplicação no servidor." |
| }, |
| { |
| id: 5, |
| title: "Tráfego suspeito", |
| source: "Firewall", |
| severity: "Média", |
| detectedAt: "2023-05-13T18:45:00", |
| status: "Resolvido", |
| details: "Foi detectado tráfego incomum vindo do IP 192.168.1.100. O firewall bloqueou automaticamente." |
| } |
| ]; |
| |
| // Rotas da API |
| app.get('/api/news', (req, res) => { |
| res.json(news); |
| }); |
| |
| app.get('/api/alerts', (req, res) => { |
| res.json(alerts); |
| }); |
| |
| app.get('/api/dashboard', (req, res) => { |
| res.json({ |
| lastUpdated: new Date().toISOString(), |
| systemStatus: "Operacional", |
| stats: { |
| newsCount: news.length, |
| alertsCount: alerts.length, |
| resolvedAlerts: alerts.filter(a => a.status === "Resolvido").length |
| } |
| }); |
| }); |
| |
| app.post('/api/denuncias', (req, res) => { |
| const { title, description, category, urgency } = req.body; |
| |
| if (!title || !description) { |
| return res.status(400).json({ error: "Título e descrição são obrigatórios" }); |
| } |
| |
| // Simula processamento |
| setTimeout(() => { |
| res.json({ |
| success: true, |
| message: "Denúncia registrada com sucesso", |
| data: { |
| id: Math.floor(Math.random() * 1000), |
| title, |
| description, |
| category, |
| urgency, |
| status: "Recebido", |
| createdAt: new Date().toISOString() |
| } |
| }); |
| }, 1000); |
| }); |
| |
| // Rota de teste |
| app.get('/', (req, res) => { |
| res.send('API DeekSeek está rodando!'); |
| }); |
| |
| // Inicia o servidor |
| app.listen(PORT, () => { |
| console.log(\`Servidor rodando em http://localhost:\${PORT}\`); |
| }); |
| `; |
| |
| |
| const instructions = ` |
| // INSTRUÇÕES PARA EXECUTAR O BACKEND: |
| |
| 1. Crie um arquivo chamado server.js e cole todo o código acima (dentro das crases) |
| 2. Certifique-se de ter o Node.js instalado (https://nodejs.org/) |
| 3. No terminal, instale as dependências necessárias: |
| npm install express cors body-parser |
| 4. Inicie o servidor: |
| node server.js |
| 5. O servidor estará disponível em http://localhost:3000 |
| 6. As rotas da API serão: |
| - GET /api/news - Lista de notícias |
| - GET /api/alerts - Lista de alertas |
| - GET /api/dashboard - Dados do dashboard |
| - POST /api/denuncias - Enviar denúncias |
| `; |
| |
| console.log('Código do servidor disponível para cópia. Veja as instruções no console.'); |
| console.log(instructions); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=dandydow/backend-final-full" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |