Files
mission-control/app/page.tsx

552 lines
18 KiB
TypeScript

"use client";
import { useState, useEffect } from 'react';
// Types
type TaskStatus = 'todo' | 'in-progress' | 'done';
type Task = { id: string; title: string; status: TaskStatus; assignee: string; priority?: string; created?: string };
type CronJob = { name: string; enabled: boolean; status: string };
type Memory = { id: string; title: string; date: string; preview: 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 [loading, setLoading] = useState(true);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
async function fetchData() {
try {
const [tasksRes, cronsRes, memoryRes] = await Promise.all([
fetch('/api/data?type=tasks'),
fetch('/api/data?type=crons'),
fetch('/api/data?type=memory')
]);
setTasks(await tasksRes.json());
setCrons(await cronsRes.json());
setMemories(await memoryRes.json());
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
return (
<div style={{
minHeight: '100vh',
background: '#0a0a0f',
color: '#e4e4e7',
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
position: 'relative',
overflow: 'hidden'
}}>
{/* Animated 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%),
radial-gradient(ellipse at 50% 50%, rgba(0, 200, 255, 0.05) 0%, transparent 70%)
`,
pointerEvents: 'none',
zIndex: 0
}} />
{/* Grid overlay */}
<div style={{
position: 'fixed',
inset: 0,
backgroundImage: `
linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px)
`,
backgroundSize: '50px 50px',
pointerEvents: 'none',
zIndex: 0
}} />
{/* Header */}
<header style={{
position: 'relative',
zIndex: 10,
padding: '15px',
borderBottom: '1px solid rgba(255,255,255,0.08)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
backdropFilter: 'blur(10px)',
background: 'rgba(10,10,15,0.8)'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
<div style={{
width: '40px',
height: '40px',
borderRadius: '10px',
background: 'linear-gradient(135deg, #ff0050 0%, #7800ff 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '20px',
boxShadow: '0 0 30px rgba(255,0,80,0.4)'
}}>
</div>
<div>
<h1 style={{
fontSize: '1rem',
fontWeight: 700,
background: 'linear-gradient(90deg, #fff 0%, #a0a0a0 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
letterSpacing: '-0.5px'
}}>
MISSION CONTROL
</h1>
<p style={{ fontSize: '0.7rem', color: '#666', letterSpacing: '2px', textTransform: 'uppercase' }}>
NodeCrew Operations Center
</p>
</div>
</div>
<nav style={{ display: 'flex', gap: '5px' }}>
{['tasks', 'crons', 'memory', 'team'].map((tab, i) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
style={{
padding: '12px 24px',
background: activeTab === tab
? 'linear-gradient(135deg, rgba(255,0,80,0.2) 0%, rgba(120,0,255,0.2) 100%)'
: 'transparent',
border: activeTab === tab ? '1px solid rgba(255,0,80,0.3)' : '1px solid transparent',
borderRadius: '8px',
color: activeTab === tab ? '#ff0050' : '#666',
cursor: 'pointer',
fontSize: '0.75rem',
textTransform: 'uppercase',
letterSpacing: '1px',
transition: 'all 0.3s ease',
backdropFilter: 'blur(10px)'
}}
>
{tab}
</button>
))}
</nav>
</header>
{/* Content */}
<main style={{
position: 'relative',
zIndex: 10,
padding: '15px',
maxWidth: '1200px',
margin: '0 auto'
}}>
{!mounted ? (
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '50vh',
flexDirection: 'column',
gap: '10px'
}}>
<div style={{
width: '60px',
height: '60px',
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.8rem', letterSpacing: '2px' }}>INITIALIZING...</p>
</div>
) : loading ? (
<div style={{ color: '#666', textAlign: 'center', padding: '100px 0' }}>
Loading live data...
</div>
) : (
<>
{activeTab === 'tasks' && <TasksBoard tasks={tasks} />}
{activeTab === 'crons' && <CronMonitor crons={crons} />}
{activeTab === 'memory' && <MemoryVault memories={memories} />}
{activeTab === 'team' && <Team />}
</>
)}
</main>
</div>
);
}
// ============ TASKS BOARD ============
function TasksBoard({ tasks }: { tasks: Task[] }) {
const columns: { status: TaskStatus; label: string; color: string; bg: string }[] = [
{ status: 'todo', label: 'Queued', color: '#fbbf24', bg: 'rgba(251, 191, 36, 0.1)' },
{ status: 'in-progress', label: 'Active', color: '#ff0050', bg: 'rgba(255, 0, 80, 0.1)' },
{ status: 'done', label: 'Complete', color: '#00ff88', bg: 'rgba(0, 255, 136, 0.1)' },
];
return (
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '10px' }}>
{columns.map((col, i) => (
<div
key={col.status}
style={{
background: 'rgba(20, 20, 30, 0.6)',
borderRadius: '16px',
padding: '25px',
border: '1px solid rgba(255,255,255,0.05)',
backdropFilter: 'blur(20px)',
animation: `fadeSlideIn 0.5s ease ${i * 0.1}s both`
}}
>
<style>{`@keyframes fadeSlideIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }`}</style>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
marginBottom: '25px',
paddingBottom: '15px',
borderBottom: '1px solid rgba(255,255,255,0.05)'
}}>
<div style={{
width: '10px',
height: '10px',
borderRadius: '50%',
background: col.color,
boxShadow: `0 0 20px ${col.color}`
}} />
<h3 style={{ color: col.color, fontSize: '0.8rem', letterSpacing: '2px', textTransform: 'uppercase' }}>
{col.label}
</h3>
<span style={{
marginLeft: 'auto',
background: col.bg,
color: col.color,
padding: '4px 10px',
borderRadius: '20px',
fontSize: '0.7rem'
}}>
{tasks.filter(t => t.status === col.status).length}
</span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{tasks.filter(t => t.status === col.status).map((task, j) => (
<div
key={task.id}
style={{
background: 'rgba(255,255,255,0.03)',
borderRadius: '10px',
padding: '15px',
border: '1px solid rgba(255,255,255,0.05)',
transition: 'all 0.2s ease',
cursor: 'pointer',
animation: `fadeIn 0.3s ease ${j * 0.05}s both`
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(255,255,255,0.06)';
e.currentTarget.style.transform = 'translateX(5px)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(255,255,255,0.03)';
e.currentTarget.style.transform = 'translateX(0)';
}}
>
<p style={{ fontSize: '0.9rem', color: '#e4e4e7', marginBottom: '8px' }}>
{task.title}
</p>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style={{
fontSize: '0.65rem',
color: '#666',
background: 'rgba(255,255,255,0.05)',
padding: '3px 8px',
borderRadius: '4px'
}}>
@{task.assignee}
</span>
{task.priority && task.priority === 'high' && (
<span style={{
fontSize: '0.6rem',
color: '#ff0050',
border: '1px solid rgba(255,0,80,0.3)',
padding: '2px 6px',
borderRadius: '4px'
}}>
HIGH
</span>
)}
</div>
</div>
))}
{tasks.filter(t => t.status === col.status).length === 0 && (
<p style={{ color: '#444', fontSize: '0.8rem', textAlign: 'center', padding: '20px 0' }}>
No tasks
</p>
)}
</div>
</div>
))}
<style>{`@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }`}</style>
</div>
);
}
// ============ CRON MONITOR ============
function CronMonitor({ crons }: { crons: CronJob[] }) {
return (
<div style={{
background: 'rgba(20, 20, 30, 0.6)',
borderRadius: '16px',
padding: '30px',
border: '1px solid rgba(255,255,255,0.05)',
backdropFilter: 'blur(20px)'
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '15px',
marginBottom: '30px'
}}>
<div style={{
width: '40px',
height: '40px',
borderRadius: '10px',
background: 'linear-gradient(135deg, #00c8ff 0%, #7800ff 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '18px'
}}>
</div>
<div>
<h2 style={{ color: '#fff', fontSize: '1rem' }}>System Cron Jobs</h2>
<p style={{ color: '#666', fontSize: '0.75rem' }}>{crons.length} active jobs</p>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '15px' }}>
{crons.map((cron, i) => (
<div
key={i}
style={{
background: 'rgba(255,255,255,0.03)',
borderRadius: '10px',
padding: '15px 20px',
border: '1px solid rgba(255,255,255,0.05)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
transition: 'all 0.2s ease'
}}
>
<span style={{ fontSize: '0.85rem', color: '#e4e4e7' }}>{cron.name}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style={{
width: '8px',
height: '8px',
borderRadius: '50%',
background: cron.status === 'ok' ? '#00ff88' : '#ff0050',
boxShadow: cron.status === 'ok' ? '0 0 10px #00ff88' : '0 0 10px #ff0050'
}} />
<span style={{
fontSize: '0.7rem',
color: cron.status === 'ok' ? '#00ff88' : '#ff0050',
textTransform: 'uppercase',
letterSpacing: '1px'
}}>
{cron.status}
</span>
</div>
</div>
))}
{crons.length === 0 && (
<p style={{ color: '#444', gridColumn: '1/-1', textAlign: 'center', padding: '40px' }}>
No cron data available
</p>
)}
</div>
</div>
);
}
// ============ MEMORY VAULT ============
function MemoryVault({ memories }: { memories: Memory[] }) {
const [search, setSearch] = useState('');
const filtered = memories.filter(m =>
m.title.toLowerCase().includes(search.toLowerCase())
);
return (
<div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '15px',
marginBottom: '30px'
}}>
<div style={{
width: '40px',
height: '40px',
borderRadius: '10px',
background: 'linear-gradient(135deg, #ff0050 0%, #ff8800 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '18px'
}}>
💾
</div>
<div>
<h2 style={{ color: '#fff', fontSize: '1rem' }}>Memory Vault</h2>
<p style={{ color: '#666', fontSize: '0.75rem' }}>{memories.length} memories stored</p>
</div>
</div>
<input
type="text"
placeholder="Search memories..."
value={search}
onChange={(e) => setSearch(e.target.value)}
style={{
width: '100%',
padding: '15px 20px',
borderRadius: '10px',
border: '1px solid rgba(255,255,255,0.1)',
background: 'rgba(20, 20, 30, 0.6)',
color: '#e4e4e7',
fontSize: '0.9rem',
marginBottom: '30px',
outline: 'none',
backdropFilter: 'blur(10px)'
}}
/>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '10px' }}>
{filtered.map((mem, i) => (
<div
key={mem.id}
style={{
background: 'rgba(20, 20, 30, 0.6)',
borderRadius: '12px',
padding: '15px',
border: '1px solid rgba(255,255,255,0.05)',
backdropFilter: 'blur(20px)',
transition: 'all 0.3s ease',
cursor: 'pointer'
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-5px)';
e.currentTarget.style.boxShadow = '0 20px 40px rgba(0,0,0,0.3)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = 'none';
}}
>
<div style={{
width: '40px',
height: '40px',
borderRadius: '10px',
background: 'linear-gradient(135deg, rgba(120,0,255,0.3) 0%, rgba(255,0,80,0.3) 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: '15px',
fontSize: '16px'
}}>
📄
</div>
<h3 style={{ color: '#fff', fontSize: '0.95rem', marginBottom: '10px' }}>{mem.title}</h3>
<p style={{ color: '#666', fontSize: '0.75rem', lineHeight: 1.6, marginBottom: '15px' }}>
{mem.preview}...
</p>
<span style={{ color: '#444', fontSize: '0.65rem', letterSpacing: '1px' }}>
{mem.date}
</span>
</div>
))}
{filtered.length === 0 && (
<p style={{ color: '#444', gridColumn: '1/-1', textAlign: 'center', padding: '40px' }}>
No memories found
</p>
)}
</div>
</div>
);
}
// ============ TEAM ============
function Team() {
const members = [
{ name: 'Jelena', role: 'Chief of Staff', status: 'online', avatar: '👩‍💼' },
{ name: 'Neo', role: 'CTO / DevOps', status: 'online', avatar: '🖥️' },
];
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '10px' }}>
{members.map((member, i) => (
<div
key={i}
style={{
background: 'rgba(20, 20, 30, 0.6)',
borderRadius: '16px',
padding: '30px',
border: '1px solid rgba(255,255,255,0.05)',
backdropFilter: 'blur(20px)',
textAlign: 'center'
}}
>
<div style={{
width: '80px',
height: '80px',
borderRadius: '50%',
background: 'linear-gradient(135deg, #7800ff 0%, #ff0050 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 20px',
fontSize: '32px',
boxShadow: '0 0 40px rgba(120,0,255,0.3)'
}}>
{member.avatar}
</div>
<h3 style={{ color: '#fff', fontSize: '1.1rem', marginBottom: '5px' }}>{member.name}</h3>
<p style={{ color: '#666', fontSize: '0.8rem', marginBottom: '15px' }}>{member.role}</p>
<span style={{
display: 'inline-flex',
alignItems: 'center',
gap: '6px',
background: 'rgba(0,255,136,0.1)',
color: '#00ff88',
padding: '6px 12px',
borderRadius: '20px',
fontSize: '0.7rem'
}}>
<span style={{
width: '6px',
height: '6px',
borderRadius: '50%',
background: '#00ff88'
}} />
{member.status}
</span>
</div>
))}
</div>
);
}