240 lines
13 KiB
TypeScript
240 lines
13 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
type TaskStatus = 'todo' | 'in-progress' | 'done';
|
|
type Task = { id: string; title: string; status: TaskStatus; assignee: string; priority?: string };
|
|
type CronJob = { name: string; enabled: boolean; status: string };
|
|
type Memory = { id: string; title: string; date: string; preview: string };
|
|
type ServerStatus = { cpu: string; ram: string; disk: string; uptime: string; containers: string };
|
|
type Backup = { name: string; status: string; lastRun: string };
|
|
type Agent = { name: string; role: string; status: string };
|
|
type WhatsAppStatus = { status: string; chats: number; lastUpdate: string };
|
|
|
|
export default function MissionControl() {
|
|
const [activeTab, setActiveTab] = useState('tasks');
|
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
const [crons, setCrons] = useState<CronJob[]>([]);
|
|
const [memories, setMemories] = useState<Memory[]>([]);
|
|
const [server, setServer] = useState<ServerStatus | null>(null);
|
|
const [backups, setBackups] = useState<Backup[]>([]);
|
|
const [agents, setAgents] = useState<Agent[]>([]);
|
|
const [whatsapp, setWhatsapp] = useState<WhatsAppStatus | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
async function fetchData() {
|
|
try {
|
|
const [tasksRes, cronsRes, memoryRes, serverRes, backupsRes, agentsRes, waRes] = await Promise.all([
|
|
fetch('/api/data?type=tasks'),
|
|
fetch('/api/data?type=crons'),
|
|
fetch('/api/data?type=memory'),
|
|
fetch('/api/data?type=server'),
|
|
fetch('/api/data?type=backups'),
|
|
fetch('/api/data?type=agents'),
|
|
fetch('/api/data?type=whatsapp')
|
|
]);
|
|
setTasks(await tasksRes.json());
|
|
setCrons(await cronsRes.json());
|
|
setMemories(await memoryRes.json());
|
|
setServer(await serverRes.json());
|
|
setBackups(await backupsRes.json());
|
|
setAgents(await agentsRes.json());
|
|
setWhatsapp(await waRes.json());
|
|
} catch (e) {
|
|
console.error(e);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
fetchData();
|
|
}, []);
|
|
|
|
const tabs = ['tasks', 'crons', 'server', 'backups', 'agents', 'whatsapp', 'memory'];
|
|
|
|
return (
|
|
<div style={{ minHeight: '100vh', background: '#0a0a0f', color: '#e4e4e7', fontFamily: "'JetBrains Mono', monospace" }}>
|
|
{/* Background */}
|
|
<div style={{ position: 'fixed', inset: 0, background: 'radial-gradient(ellipse at 20% 20%, rgba(120,0,255,0.15) 0%, transparent 50%), radial-gradient(ellipse at 80% 80%, rgba(255,0,80,0.1) 0%, transparent 50%)', pointerEvents: 'none', zIndex: 0 }} />
|
|
|
|
{/* Header */}
|
|
<header style={{ position: 'relative', zIndex: 10, padding: '15px', borderBottom: '1px solid rgba(255,255,255,0.08)', background: 'rgba(10,10,15,0.8)', backdropFilter: 'blur(10px)' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
|
<div style={{ width: '32px', height: '32px', borderRadius: '8px', background: 'linear-gradient(135deg, #ff0050 0%, #7800ff 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px' }}>⬡</div>
|
|
<h1 style={{ fontSize: '1rem', fontWeight: 700, background: 'linear-gradient(90deg, #fff 0%, #a0a0a0 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>MISSION CONTROL</h1>
|
|
</div>
|
|
<nav style={{ display: 'flex', gap: '3px', flexWrap: 'wrap' }}>
|
|
{tabs.map(tab => (
|
|
<button key={tab} onClick={() => setActiveTab(tab)} style={{ padding: '8px 12px', background: activeTab === tab ? 'rgba(255,0,80,0.2)' : 'transparent', border: activeTab === tab ? '1px solid rgba(255,0,80,0.3)' : '1px solid transparent', borderRadius: '6px', color: activeTab === tab ? '#ff0050' : '#666', cursor: 'pointer', fontSize: '0.65rem', textTransform: 'uppercase', letterSpacing: '0.5px' }}>{tab}</button>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Content */}
|
|
<main style={{ position: 'relative', zIndex: 10, padding: '15px', maxWidth: '1200px', margin: '0 auto' }}>
|
|
{!mounted || loading ? (
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '50vh', flexDirection: 'column', gap: '15px' }}>
|
|
<div style={{ width: '40px', height: '40px', border: '3px solid rgba(255,0,80,0.2)', borderTopColor: '#ff0050', borderRadius: '50%', animation: 'spin 1s linear infinite' }} />
|
|
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
|
<p style={{ color: '#666', fontSize: '0.7rem' }}>INITIALIZING...</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{activeTab === 'tasks' && <TasksBoard tasks={tasks} />}
|
|
{activeTab === 'crons' && <CronMonitor crons={crons} />}
|
|
{activeTab === 'server' && <ServerStatus server={server} />}
|
|
{activeTab === 'backups' && <BackupStatus backups={backups} />}
|
|
{activeTab === 'agents' && <AgentStatus agents={agents} />}
|
|
{activeTab === 'whatsapp' && <WhatsAppStatus whatsapp={whatsapp} />}
|
|
{activeTab === 'memory' && <MemoryVault memories={memories} />}
|
|
</>
|
|
)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TasksBoard({ tasks }: { tasks: Task[] }) {
|
|
const cols: { status: TaskStatus; label: string; color: string }[] = [
|
|
{ status: 'todo', label: 'Queued', color: '#fbbf24' },
|
|
{ status: 'in-progress', label: 'Active', color: '#ff0050' },
|
|
{ status: 'done', label: 'Complete', color: '#00ff88' },
|
|
];
|
|
return (
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '15px' }}>
|
|
{cols.map(col => (
|
|
<div key={col.status} style={{ background: 'rgba(20,20,30,0.6)', borderRadius: '12px', padding: '15px', border: '1px solid rgba(255,255,255,0.05)' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '15px', paddingBottom: '10px', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
|
|
<div style={{ width: '8px', height: '8px', borderRadius: '50%', background: col.color, boxShadow: `0 0 10px ${col.color}` }} />
|
|
<span style={{ color: col.color, fontSize: '0.7rem', textTransform: 'uppercase', letterSpacing: '1px' }}>{col.label}</span>
|
|
<span style={{ marginLeft: 'auto', background: 'rgba(255,255,255,0.05)', padding: '2px 8px', borderRadius: '10px', fontSize: '0.65rem' }}>{tasks.filter(t => t.status === col.status).length}</span>
|
|
</div>
|
|
{tasks.filter(t => t.status === col.status).map(task => (
|
|
<div key={task.id} style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '8px', padding: '10px', marginBottom: '8px' }}>
|
|
<p style={{ fontSize: '0.8rem', marginBottom: '5px' }}>{task.title}</p>
|
|
<span style={{ fontSize: '0.6rem', color: '#666' }}>@{task.assignee}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function CronMonitor({ crons }: { crons: CronJob[] }) {
|
|
return (
|
|
<div style={{ background: 'rgba(20,20,30,0.6)', borderRadius: '12px', padding: '15px', border: '1px solid rgba(255,255,255,0.05)' }}>
|
|
<h2 style={{ fontSize: '0.9rem', marginBottom: '15px' }}>⏱ Cron Jobs ({crons.length})</h2>
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '10px' }}>
|
|
{crons.map((cron, i) => (
|
|
<div key={i} style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '8px', padding: '12px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span style={{ fontSize: '0.75rem' }}>{cron.name}</span>
|
|
<span style={{ width: '6px', height: '6px', borderRadius: '50%', background: cron.status === 'ok' ? '#00ff88' : '#ff0050' }} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ServerStatus({ server }: { server: ServerStatus | null }) {
|
|
if (!server) return <p style={{ color: '#666' }}>Loading...</p>;
|
|
return (
|
|
<div style={{ background: 'rgba(20,20,30,0.6)', borderRadius: '12px', padding: '15px', border: '1px solid rgba(255,255,255,0.05)' }}>
|
|
<h2 style={{ fontSize: '0.9rem', marginBottom: '15px' }}>🖥 Server Status</h2>
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '10px' }}>
|
|
{Object.entries(server).map(([key, value]) => (
|
|
<div key={key} style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '8px', padding: '12px' }}>
|
|
<p style={{ color: '#666', fontSize: '0.65rem', textTransform: 'uppercase', marginBottom: '5px' }}>{key}</p>
|
|
<p style={{ fontSize: '0.85rem' }}>{value}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function BackupStatus({ backups }: { backups: Backup[] }) {
|
|
return (
|
|
<div style={{ background: 'rgba(20,20,30,0.6)', borderRadius: '12px', padding: '15px', border: '1px solid rgba(255,255,255,0.05)' }}>
|
|
<h2 style={{ fontSize: '0.9rem', marginBottom: '15px' }}>💾 Backup Status</h2>
|
|
{backups.map((backup, i) => (
|
|
<div key={i} style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '8px', padding: '12px', marginBottom: '10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<div>
|
|
<p style={{ fontSize: '0.85rem' }}>{backup.name}</p>
|
|
<p style={{ fontSize: '0.65rem', color: '#666' }}>{backup.lastRun}</p>
|
|
</div>
|
|
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: backup.status === 'ok' ? '#00ff88' : '#ff0050' }} />
|
|
</div>
|
|
))}
|
|
{backups.length === 0 && <p style={{ color: '#666', fontSize: '0.8rem' }}>No backups found</p>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AgentStatus({ agents }: { agents: Agent[] }) {
|
|
return (
|
|
<div style={{ background: 'rgba(20,20,30,0.6)', borderRadius: '12px', padding: '15px', border: '1px solid rgba(255,255,255,0.05)' }}>
|
|
<h2 style={{ fontSize: '0.9rem', marginBottom: '15px' }}>🤖 Agents</h2>
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '10px' }}>
|
|
{agents.map((agent, i) => (
|
|
<div key={i} style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '8px', padding: '15px', display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
<div style={{ width: '40px', height: '40px', borderRadius: '50%', background: 'linear-gradient(135deg, #7800ff 0%, #ff0050 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px' }}>{agent.name[0]}</div>
|
|
<div>
|
|
<p style={{ fontSize: '0.85rem' }}>{agent.name}</p>
|
|
<p style={{ fontSize: '0.65rem', color: '#666' }}>{agent.role}</p>
|
|
<span style={{ fontSize: '0.6rem', color: agent.status === 'online' ? '#00ff88' : '#666' }}>● {agent.status}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function WhatsAppStatus({ whatsapp }: { whatsapp: WhatsAppStatus | null }) {
|
|
if (!whatsapp) return <p style={{ color: '#666' }}>Loading...</p>;
|
|
return (
|
|
<div style={{ background: 'rgba(20,20,30,0.6)', borderRadius: '12px', padding: '15px', border: '1px solid rgba(255,255,255,0.05)' }}>
|
|
<h2 style={{ fontSize: '0.9rem', marginBottom: '15px' }}>💬 WhatsApp Chats</h2>
|
|
<div style={{ background: 'rgba(255,255,255,0.03)', borderRadius: '8px', padding: '15px' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}>
|
|
<span style={{ color: '#666', fontSize: '0.75rem' }}>Status</span>
|
|
<span style={{ color: whatsapp.status === 'ok' ? '#00ff88' : '#ff0050', fontSize: '0.75rem' }}>{whatsapp.status}</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}>
|
|
<span style={{ color: '#666', fontSize: '0.75rem' }}>Chats</span>
|
|
<span style={{ fontSize: '0.75rem' }}>{whatsapp.chats}</span>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span style={{ color: '#666', fontSize: '0.75rem' }}>Last Update</span>
|
|
<span style={{ fontSize: '0.75rem' }}>{whatsapp.lastUpdate}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MemoryVault({ memories }: { memories: Memory[] }) {
|
|
const [search, setSearch] = useState('');
|
|
const filtered = memories.filter(m => m.title.toLowerCase().includes(search.toLowerCase()));
|
|
return (
|
|
<div>
|
|
<input type="text" placeholder="Search memories..." value={search} onChange={e => setSearch(e.target.value)} style={{ width: '100%', padding: '10px', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.1)', background: 'rgba(20,20,30,0.6)', color: '#e4e4e7', fontSize: '0.8rem', marginBottom: '15px', outline: 'none' }} />
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '10px' }}>
|
|
{filtered.map((mem, i) => (
|
|
<div key={i} style={{ background: 'rgba(20,20,30,0.6)', borderRadius: '8px', padding: '12px' }}>
|
|
<p style={{ fontSize: '0.8rem', marginBottom: '5px' }}>{mem.title}</p>
|
|
<span style={{ fontSize: '0.6rem', color: '#444' }}>{mem.date}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
// Force rebuild Thu Feb 19 11:59:30 AM UTC 2026
|