diff --git a/dashboard/next.config.js b/dashboard/next.config.js
new file mode 100644
index 0000000..5cd8cc3
--- /dev/null
+++ b/dashboard/next.config.js
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ output: 'standalone',
+}
+
+module.exports = nextConfig
diff --git a/dashboard/package.json b/dashboard/package.json
new file mode 100644
index 0000000..7493da4
--- /dev/null
+++ b/dashboard/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "ophion-dashboard",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev -p 3000",
+ "build": "next build",
+ "start": "next start -p 3000",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "next": "14.1.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "next-auth": "^4.24.0",
+ "@tanstack/react-query": "^5.17.0",
+ "recharts": "^2.10.0",
+ "lucide-react": "^0.312.0",
+ "clsx": "^2.1.0",
+ "tailwind-merge": "^2.2.0",
+ "date-fns": "^3.2.0",
+ "zustand": "^4.4.0"
+ },
+ "devDependencies": {
+ "typescript": "^5.3.0",
+ "@types/node": "^20.11.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "autoprefixer": "^10.4.0",
+ "postcss": "^8.4.0",
+ "tailwindcss": "^3.4.0"
+ }
+}
diff --git a/dashboard/postcss.config.js b/dashboard/postcss.config.js
new file mode 100644
index 0000000..33ad091
--- /dev/null
+++ b/dashboard/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/dashboard/src/app/globals.css b/dashboard/src/app/globals.css
new file mode 100644
index 0000000..03679e3
--- /dev/null
+++ b/dashboard/src/app/globals.css
@@ -0,0 +1,63 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --background: 2 6 23;
+ --foreground: 248 250 252;
+ --card: 15 23 42;
+ --card-foreground: 248 250 252;
+ --primary: 34 197 94;
+ --primary-foreground: 255 255 255;
+ --secondary: 139 92 246;
+ --muted: 51 65 85;
+ --muted-foreground: 148 163 184;
+ --accent: 34 197 94;
+ --destructive: 239 68 68;
+ --border: 51 65 85;
+ --ring: 34 197 94;
+}
+
+body {
+ background: rgb(var(--background));
+ color: rgb(var(--foreground));
+}
+
+/* Custom scrollbar */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: rgb(15, 23, 42);
+}
+
+::-webkit-scrollbar-thumb {
+ background: rgb(51, 65, 85);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: rgb(71, 85, 105);
+}
+
+/* Animations */
+@keyframes pulse-glow {
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.4); }
+ 50% { box-shadow: 0 0 0 8px rgba(34, 197, 94, 0); }
+}
+
+.pulse-glow {
+ animation: pulse-glow 2s infinite;
+}
+
+/* Card hover effects */
+.card-hover {
+ transition: all 0.2s ease;
+}
+
+.card-hover:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
+}
diff --git a/dashboard/src/app/layout.tsx b/dashboard/src/app/layout.tsx
new file mode 100644
index 0000000..02a8fcd
--- /dev/null
+++ b/dashboard/src/app/layout.tsx
@@ -0,0 +1,24 @@
+import type { Metadata } from 'next'
+import { Inter } from 'next/font/google'
+import './globals.css'
+
+const inter = Inter({ subsets: ['latin'] })
+
+export const metadata: Metadata = {
+ title: 'OPHION Dashboard',
+ description: 'Observability Platform with AI',
+}
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/dashboard/src/app/page.tsx b/dashboard/src/app/page.tsx
new file mode 100644
index 0000000..5a4aaa0
--- /dev/null
+++ b/dashboard/src/app/page.tsx
@@ -0,0 +1,134 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import Sidebar from '@/components/layout/Sidebar'
+import Header from '@/components/layout/Header'
+import MetricCard from '@/components/ui/MetricCard'
+import HostsTable from '@/components/ui/HostsTable'
+import AlertsList from '@/components/ui/AlertsList'
+import CpuChart from '@/components/charts/CpuChart'
+import MemoryChart from '@/components/charts/MemoryChart'
+import AIInsights from '@/components/ui/AIInsights'
+import Copilot from '@/components/ui/Copilot'
+
+export default function Dashboard() {
+ const [showCopilot, setShowCopilot] = useState(false)
+ const [metrics, setMetrics] = useState({
+ totalHosts: 0,
+ healthyHosts: 0,
+ activeAlerts: 0,
+ cpuAvg: 0,
+ memoryAvg: 0,
+ diskAvg: 0,
+ })
+
+ // Simulated data - replace with real API calls
+ useEffect(() => {
+ setMetrics({
+ totalHosts: 12,
+ healthyHosts: 11,
+ activeAlerts: 3,
+ cpuAvg: 42.5,
+ memoryAvg: 68.3,
+ diskAvg: 54.2,
+ })
+ }, [])
+
+ return (
+
+ {/* Sidebar */}
+
+
+ {/* Main Content */}
+
+
+ {/* AI Copilot Sidebar */}
+ {showCopilot && (
+
setShowCopilot(false)} />
+ )}
+
+ )
+}
diff --git a/dashboard/src/components/charts/CpuChart.tsx b/dashboard/src/components/charts/CpuChart.tsx
new file mode 100644
index 0000000..2e6eab7
--- /dev/null
+++ b/dashboard/src/components/charts/CpuChart.tsx
@@ -0,0 +1,64 @@
+'use client'
+
+import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, Area, AreaChart } from 'recharts'
+
+const data = [
+ { time: '00:00', value: 35 },
+ { time: '02:00', value: 28 },
+ { time: '04:00', value: 22 },
+ { time: '06:00', value: 25 },
+ { time: '08:00', value: 45 },
+ { time: '10:00', value: 62 },
+ { time: '12:00', value: 58 },
+ { time: '14:00', value: 72 },
+ { time: '16:00', value: 68 },
+ { time: '18:00', value: 55 },
+ { time: '20:00', value: 48 },
+ { time: '22:00', value: 42 },
+]
+
+export default function CpuChart() {
+ return (
+
+
+
+
+
+
+
+
+
+ `${value}%`}
+ domain={[0, 100]}
+ />
+ [`${value}%`, 'CPU']}
+ />
+
+
+
+ )
+}
diff --git a/dashboard/src/components/charts/MemoryChart.tsx b/dashboard/src/components/charts/MemoryChart.tsx
new file mode 100644
index 0000000..5c7d629
--- /dev/null
+++ b/dashboard/src/components/charts/MemoryChart.tsx
@@ -0,0 +1,64 @@
+'use client'
+
+import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
+
+const data = [
+ { time: '00:00', value: 58 },
+ { time: '02:00', value: 55 },
+ { time: '04:00', value: 52 },
+ { time: '06:00', value: 54 },
+ { time: '08:00', value: 62 },
+ { time: '10:00', value: 68 },
+ { time: '12:00', value: 72 },
+ { time: '14:00', value: 78 },
+ { time: '16:00', value: 75 },
+ { time: '18:00', value: 70 },
+ { time: '20:00', value: 65 },
+ { time: '22:00', value: 60 },
+]
+
+export default function MemoryChart() {
+ return (
+
+
+
+
+
+
+
+
+
+ `${value}%`}
+ domain={[0, 100]}
+ />
+ [`${value}%`, 'Memory']}
+ />
+
+
+
+ )
+}
diff --git a/dashboard/src/components/layout/Header.tsx b/dashboard/src/components/layout/Header.tsx
new file mode 100644
index 0000000..1c345ab
--- /dev/null
+++ b/dashboard/src/components/layout/Header.tsx
@@ -0,0 +1,68 @@
+'use client'
+
+import { useState } from 'react'
+
+interface HeaderProps {
+ onCopilotClick: () => void
+}
+
+export default function Header({ onCopilotClick }: HeaderProps) {
+ const [searchQuery, setSearchQuery] = useState('')
+
+ return (
+
+ {/* Search */}
+
+
+
+ 🔍
+
+ setSearchQuery(e.target.value)}
+ className="w-full pl-10 pr-4 py-2 bg-slate-800 border border-slate-700 rounded-lg focus:outline-none focus:border-green-500 text-sm"
+ />
+
+ ⌘K
+
+
+
+
+ {/* Right Side */}
+
+ {/* Time Range */}
+
+
+ {/* Refresh */}
+
+
+ {/* Notifications */}
+
+
+ {/* AI Copilot Button */}
+
+
+
+ )
+}
diff --git a/dashboard/src/components/layout/Sidebar.tsx b/dashboard/src/components/layout/Sidebar.tsx
new file mode 100644
index 0000000..3e9fd62
--- /dev/null
+++ b/dashboard/src/components/layout/Sidebar.tsx
@@ -0,0 +1,96 @@
+'use client'
+
+import Link from 'next/link'
+import { usePathname } from 'next/navigation'
+
+const menuItems = [
+ { name: 'Overview', icon: '📊', href: '/' },
+ { name: 'Hosts', icon: '🖥️', href: '/hosts' },
+ { name: 'Containers', icon: '🐳', href: '/containers' },
+ { name: 'Metrics', icon: '📈', href: '/metrics' },
+ { name: 'Logs', icon: '📝', href: '/logs' },
+ { name: 'Traces', icon: '🔍', href: '/traces' },
+ { name: 'Alerts', icon: '🚨', href: '/alerts' },
+ { name: 'Dashboards', icon: '📋', href: '/dashboards' },
+]
+
+const bottomItems = [
+ { name: 'AI Insights', icon: '🤖', href: '/ai' },
+ { name: 'Settings', icon: '⚙️', href: '/settings' },
+]
+
+export default function Sidebar() {
+ const pathname = usePathname()
+
+ return (
+
+ )
+}
diff --git a/dashboard/src/components/ui/AIInsights.tsx b/dashboard/src/components/ui/AIInsights.tsx
new file mode 100644
index 0000000..2b77e9f
--- /dev/null
+++ b/dashboard/src/components/ui/AIInsights.tsx
@@ -0,0 +1,78 @@
+'use client'
+
+const insights = [
+ {
+ type: 'anomaly',
+ icon: '🔍',
+ title: 'Anomalia Detectada',
+ description: 'O servidor cache-01 está com consumo 40% acima do padrão para este horário.',
+ severity: 'high',
+ action: 'Investigar',
+ },
+ {
+ type: 'prediction',
+ icon: '🔮',
+ title: 'Previsão de Capacidade',
+ description: 'O disco do servidor db-01 vai atingir 90% em aproximadamente 12 dias.',
+ severity: 'medium',
+ action: 'Planejar expansão',
+ },
+ {
+ type: 'optimization',
+ icon: '💡',
+ title: 'Oportunidade de Economia',
+ description: 'O servidor dev-02 está subutilizado (CPU média: 5%). Considere reduzir recursos.',
+ severity: 'low',
+ action: 'Ver detalhes',
+ },
+]
+
+export default function AIInsights() {
+ const getSeverityColor = (severity: string) => {
+ switch (severity) {
+ case 'high':
+ return 'border-red-500/50 bg-red-950/20'
+ case 'medium':
+ return 'border-yellow-500/50 bg-yellow-950/20'
+ default:
+ return 'border-green-500/50 bg-green-950/20'
+ }
+ }
+
+ return (
+
+
+
+
🤖
+
+
AI Insights
+
Análises geradas automaticamente
+
+
+
+
+
+
+ {insights.map((insight, index) => (
+
+
+
{insight.icon}
+
+
{insight.title}
+
{insight.description}
+
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/dashboard/src/components/ui/AlertsList.tsx b/dashboard/src/components/ui/AlertsList.tsx
new file mode 100644
index 0000000..c9769fb
--- /dev/null
+++ b/dashboard/src/components/ui/AlertsList.tsx
@@ -0,0 +1,85 @@
+'use client'
+
+const alerts = [
+ {
+ id: 1,
+ severity: 'critical',
+ title: 'High CPU Usage',
+ host: 'cache-01',
+ message: 'CPU at 92% for 10 minutes',
+ time: '2 min ago',
+ aiSuggestion: 'Consider restarting Redis or scaling horizontally',
+ },
+ {
+ id: 2,
+ severity: 'critical',
+ title: 'Memory Critical',
+ host: 'cache-01',
+ message: 'Memory at 94%',
+ time: '5 min ago',
+ aiSuggestion: 'Memory leak detected. Recommended: restart service',
+ },
+ {
+ id: 3,
+ severity: 'warning',
+ title: 'High Memory Usage',
+ host: 'api-01',
+ message: 'Memory at 85%',
+ time: '15 min ago',
+ aiSuggestion: 'Monitor for next hour before action',
+ },
+]
+
+export default function AlertsList() {
+ const getSeverityStyles = (severity: string) => {
+ switch (severity) {
+ case 'critical':
+ return 'border-l-red-500 bg-red-950/30'
+ case 'warning':
+ return 'border-l-yellow-500 bg-yellow-950/30'
+ default:
+ return 'border-l-blue-500 bg-blue-950/30'
+ }
+ }
+
+ const getSeverityIcon = (severity: string) => {
+ switch (severity) {
+ case 'critical':
+ return '🔴'
+ case 'warning':
+ return '🟡'
+ default:
+ return '🔵'
+ }
+ }
+
+ return (
+
+ {alerts.map((alert) => (
+
+
+
+
{getSeverityIcon(alert.severity)}
+
+
{alert.title}
+
+ {alert.host} • {alert.message}
+
+ {alert.aiSuggestion && (
+
+ 🤖
+ {alert.aiSuggestion}
+
+ )}
+
+
+
{alert.time}
+
+
+ ))}
+
+ )
+}
diff --git a/dashboard/src/components/ui/Copilot.tsx b/dashboard/src/components/ui/Copilot.tsx
new file mode 100644
index 0000000..e0c526c
--- /dev/null
+++ b/dashboard/src/components/ui/Copilot.tsx
@@ -0,0 +1,251 @@
+'use client'
+
+import { useState, useRef, useEffect } from 'react'
+
+interface Message {
+ role: 'user' | 'assistant'
+ content: string
+ actions?: Array<{
+ type: string
+ description: string
+ command?: string
+ }>
+}
+
+interface CopilotProps {
+ onClose: () => void
+}
+
+export default function Copilot({ onClose }: CopilotProps) {
+ const [messages, setMessages] = useState([
+ {
+ role: 'assistant',
+ content: 'Olá! Sou o OPHION Copilot. Posso ajudar com análise de métricas, troubleshooting, ou qualquer dúvida sobre sua infraestrutura. Como posso ajudar?',
+ },
+ ])
+ const [input, setInput] = useState('')
+ const [isLoading, setIsLoading] = useState(false)
+ const messagesEndRef = useRef(null)
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
+ }
+
+ useEffect(() => {
+ scrollToBottom()
+ }, [messages])
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!input.trim() || isLoading) return
+
+ const userMessage = input.trim()
+ setInput('')
+ setMessages((prev) => [...prev, { role: 'user', content: userMessage }])
+ setIsLoading(true)
+
+ // Simulate AI response - replace with real API call
+ setTimeout(() => {
+ const response = generateMockResponse(userMessage)
+ setMessages((prev) => [...prev, response])
+ setIsLoading(false)
+ }, 1500)
+ }
+
+ const generateMockResponse = (query: string): Message => {
+ const lowerQuery = query.toLowerCase()
+
+ if (lowerQuery.includes('cpu') || lowerQuery.includes('lento')) {
+ return {
+ role: 'assistant',
+ content: `Analisei as métricas do servidor cache-01 que está com CPU alta (92%).
+
+**Causa provável:** O processo Redis está consumindo mais CPU que o normal, provavelmente devido a um aumento de requisições ou operações de BGSAVE.
+
+**Métricas relacionadas:**
+- Conexões ativas: 1,247 (média: 800)
+- Operações/s: 45,000 (média: 30,000)
+- Memory: 94% (próximo do limite)
+
+**Recomendações:**`,
+ actions: [
+ {
+ type: 'command',
+ description: 'Reiniciar Redis gracefully',
+ command: 'redis-cli SHUTDOWN NOSAVE && systemctl start redis',
+ },
+ {
+ type: 'scale',
+ description: 'Adicionar réplica Redis',
+ command: 'kubectl scale deployment redis --replicas=2',
+ },
+ ],
+ }
+ }
+
+ if (lowerQuery.includes('disco') || lowerQuery.includes('storage')) {
+ return {
+ role: 'assistant',
+ content: `Analisei o uso de disco dos seus servidores:
+
+**Previsões de capacidade:**
+- **db-01:** 68% usado, vai atingir 90% em ~12 dias
+- **web-01:** 34% usado, estável
+- **api-01:** 52% usado, crescendo 2%/dia
+
+**Maiores consumidores em db-01:**
+- /var/lib/postgresql: 45GB
+- /var/log: 12GB
+- /tmp: 3GB
+
+**Sugestão:** Rotacionar logs antigos e configurar retenção.`,
+ actions: [
+ {
+ type: 'command',
+ description: 'Limpar logs antigos',
+ command: 'find /var/log -type f -mtime +7 -delete',
+ },
+ ],
+ }
+ }
+
+ return {
+ role: 'assistant',
+ content: `Entendi sua pergunta sobre "${query}".
+
+Posso ajudar com:
+- 📊 Análise de métricas (CPU, memória, disco, rede)
+- 🔍 Investigação de problemas
+- 📝 Análise de logs
+- 🚨 Explicação de alertas
+- 💡 Sugestões de otimização
+
+O que você gostaria de saber especificamente?`,
+ }
+ }
+
+ const quickActions = [
+ 'Por que o cache-01 está lento?',
+ 'Analise os alertas ativos',
+ 'Previsão de disco',
+ 'Resumo das últimas 24h',
+ ]
+
+ return (
+
+ {/* Header */}
+
+
+
🤖
+
+
OPHION Copilot
+
+
+ Online
+
+
+
+
+
+
+ {/* Messages */}
+
+ {messages.map((message, index) => (
+
+
+
{message.content}
+
+ {message.actions && message.actions.length > 0 && (
+
+ {message.actions.map((action, actionIndex) => (
+
+
{action.description}
+ {action.command && (
+
+ {action.command}
+
+ )}
+
+
+ ))}
+
+ )}
+
+
+ ))}
+
+ {isLoading && (
+
+ )}
+
+
+
+
+ {/* Quick Actions */}
+ {messages.length === 1 && (
+
+
Perguntas frequentes:
+
+ {quickActions.map((action, index) => (
+
+ ))}
+
+
+ )}
+
+ {/* Input */}
+
+
+ )
+}
diff --git a/dashboard/src/components/ui/HostsTable.tsx b/dashboard/src/components/ui/HostsTable.tsx
new file mode 100644
index 0000000..292139d
--- /dev/null
+++ b/dashboard/src/components/ui/HostsTable.tsx
@@ -0,0 +1,76 @@
+'use client'
+
+const hosts = [
+ { name: 'web-01', status: 'healthy', cpu: 45, memory: 62, disk: 34 },
+ { name: 'web-02', status: 'healthy', cpu: 38, memory: 58, disk: 41 },
+ { name: 'api-01', status: 'warning', cpu: 78, memory: 85, disk: 52 },
+ { name: 'db-01', status: 'healthy', cpu: 22, memory: 71, disk: 68 },
+ { name: 'cache-01', status: 'critical', cpu: 92, memory: 94, disk: 45 },
+]
+
+export default function HostsTable() {
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'healthy':
+ return 'bg-green-500'
+ case 'warning':
+ return 'bg-yellow-500'
+ case 'critical':
+ return 'bg-red-500'
+ default:
+ return 'bg-slate-500'
+ }
+ }
+
+ const getMetricColor = (value: number) => {
+ if (value >= 90) return 'text-red-400'
+ if (value >= 70) return 'text-yellow-400'
+ return 'text-green-400'
+ }
+
+ return (
+
+
+
+
+ | Host |
+ Status |
+ CPU |
+ Memory |
+ Disk |
+
+
+
+ {hosts.map((host) => (
+
+ |
+
+ 🖥️
+ {host.name}
+
+ |
+
+
+
+ {host.status}
+
+ |
+
+ {host.cpu}%
+ |
+
+ {host.memory}%
+ |
+
+ {host.disk}%
+ |
+
+ ))}
+
+
+
+ )
+}
diff --git a/dashboard/src/components/ui/MetricCard.tsx b/dashboard/src/components/ui/MetricCard.tsx
new file mode 100644
index 0000000..929d7fc
--- /dev/null
+++ b/dashboard/src/components/ui/MetricCard.tsx
@@ -0,0 +1,57 @@
+'use client'
+
+interface MetricCardProps {
+ title: string
+ value: string | number
+ subtitle?: string
+ icon: string
+ trend?: 'up' | 'down' | 'stable'
+ alert?: boolean
+}
+
+export default function MetricCard({ title, value, subtitle, icon, trend, alert }: MetricCardProps) {
+ const getTrendIcon = () => {
+ switch (trend) {
+ case 'up':
+ return ↑
+ case 'down':
+ return ↓
+ default:
+ return →
+ }
+ }
+
+ return (
+
+
+
+
{title}
+
{value}
+ {subtitle && (
+
+ {getTrendIcon()}
+ {subtitle}
+
+ )}
+
+
+ {icon}
+
+
+ {alert && (
+
+
+ Needs attention
+
+ )}
+
+ )
+}
diff --git a/dashboard/tailwind.config.js b/dashboard/tailwind.config.js
new file mode 100644
index 0000000..24b939f
--- /dev/null
+++ b/dashboard/tailwind.config.js
@@ -0,0 +1,13 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: 'class',
+ content: [
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
diff --git a/dashboard/tsconfig.json b/dashboard/tsconfig.json
new file mode 100644
index 0000000..49e4cf3
--- /dev/null
+++ b/dashboard/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [{ "name": "next" }],
+ "paths": { "@/*": ["./src/*"] }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}