dashmaster / index.html
dandydow's picture
Add 2 files
40fea8a verified
<!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 = process.env.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 -->
${Sidebar({ activeSection, setActiveSection })}
<!-- Main Content -->
<div class="flex-1 overflow-auto">
<!-- Header -->
${Header({
activeSection,
darkMode,
toggleDarkMode,
isLoading
})}
<!-- Main Content -->
<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">
<!-- Quick Stats -->
<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>
<!-- Recent Activity -->
${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">
<!-- News List -->
<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>
<!-- News Details -->
<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">
<!-- Alerts List -->
<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>
<!-- Alert Details -->
<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>
<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/dashmaster" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>