Redesign: Rich cyberpunk aesthetic with animations
This commit is contained in:
@@ -1,10 +1,53 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap');
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
html, body {
|
||||||
background: #0f0f23;
|
background: #0a0a0f;
|
||||||
color: white;
|
color: #e4e4e7;
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255,255,255,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection */
|
||||||
|
::selection {
|
||||||
|
background: rgba(255, 0, 80, 0.3);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow {
|
||||||
|
0%, 100% { box-shadow: 0 0 20px rgba(255, 0, 80, 0.3); }
|
||||||
|
50% { box-shadow: 0 0 40px rgba(255, 0, 80, 0.5); }
|
||||||
}
|
}
|
||||||
|
|||||||
592
app/page.tsx
592
app/page.tsx
@@ -4,37 +4,33 @@ import { useState, useEffect } from 'react';
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
type TaskStatus = 'todo' | 'in-progress' | 'done';
|
type TaskStatus = 'todo' | 'in-progress' | 'done';
|
||||||
type Task = { id: string; title: string; status: TaskStatus; assignee: 'me' | 'jelena' | 'neo'; priority?: string; created?: string };
|
type Task = { id: string; title: string; status: TaskStatus; assignee: string; priority?: string; created?: string };
|
||||||
|
|
||||||
type ContentStage = 'idea' | 'script' | 'thumbnail' | 'filming' | 'done';
|
|
||||||
type ContentItem = { id: string; title: string; stage: ContentStage };
|
|
||||||
|
|
||||||
type CronJob = { name: string; enabled: boolean; status: string; lastRun?: string };
|
|
||||||
|
|
||||||
|
type CronJob = { name: string; enabled: boolean; status: string };
|
||||||
type Memory = { id: string; title: string; date: string; preview: string };
|
type Memory = { id: string; title: string; date: string; preview: string };
|
||||||
|
|
||||||
type TeamMember = { id: string; name: string; role: string; status: 'working' | 'idle' };
|
|
||||||
|
|
||||||
export default function MissionControl() {
|
export default function MissionControl() {
|
||||||
const [activeTab, setActiveTab] = useState('tasks');
|
const [activeTab, setActiveTab] = useState('tasks');
|
||||||
const [tasks, setTasks] = useState<Task[]>([]);
|
const [tasks, setTasks] = useState<Task[]>([]);
|
||||||
const [crons, setCrons] = useState<CronJob[]>([]);
|
const [crons, setCrons] = useState<CronJob[]>([]);
|
||||||
|
const [memories, setMemories] = useState<Memory[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
// Fetch tasks from mounted volume
|
const [tasksRes, cronsRes, memoryRes] = await Promise.all([
|
||||||
const tasksRes = await fetch('/api/data?type=tasks');
|
fetch('/api/data?type=tasks'),
|
||||||
const tasksData = await tasksRes.json();
|
fetch('/api/data?type=crons'),
|
||||||
setTasks(tasksData);
|
fetch('/api/data?type=memory')
|
||||||
|
]);
|
||||||
// Fetch cron jobs
|
setTasks(await tasksRes.json());
|
||||||
const cronsRes = await fetch('/api/data?type=crons');
|
setCrons(await cronsRes.json());
|
||||||
const cronsData = await cronsRes.json();
|
setMemories(await memoryRes.json());
|
||||||
setCrons(cronsData);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to fetch data:', e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -43,23 +39,102 @@ export default function MissionControl() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ fontFamily: 'system-ui, sans-serif', minHeight: '100vh', background: '#0f0f23', color: 'white' }}>
|
<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 */}
|
||||||
<header style={{ padding: '20px 40px', borderBottom: '1px solid #1e1e3f', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<header style={{
|
||||||
<h1 style={{ fontSize: '1.8rem', fontWeight: 700 }}>🎯 Mission Control</h1>
|
position: 'relative',
|
||||||
<nav style={{ display: 'flex', gap: '10px' }}>
|
zIndex: 10,
|
||||||
{['tasks', 'crons', 'team', 'memory'].map(tab => (
|
padding: '30px 50px',
|
||||||
|
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: '1.5rem',
|
||||||
|
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
|
<button
|
||||||
key={tab}
|
key={tab}
|
||||||
onClick={() => setActiveTab(tab)}
|
onClick={() => setActiveTab(tab)}
|
||||||
style={{
|
style={{
|
||||||
padding: '10px 20px',
|
padding: '12px 24px',
|
||||||
background: activeTab === tab ? '#e94560' : 'transparent',
|
background: activeTab === tab
|
||||||
border: 'none',
|
? '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',
|
borderRadius: '8px',
|
||||||
color: 'white',
|
color: activeTab === tab ? '#ff0050' : '#666',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
textTransform: 'capitalize'
|
fontSize: '0.75rem',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '1px',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
backdropFilter: 'blur(10px)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tab}
|
{tab}
|
||||||
@@ -69,15 +144,43 @@ export default function MissionControl() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<main style={{ padding: '40px' }}>
|
<main style={{
|
||||||
{loading ? (
|
position: 'relative',
|
||||||
<p style={{ color: '#9ca3af' }}>Loading live data...</p>
|
zIndex: 10,
|
||||||
|
padding: '40px 50px',
|
||||||
|
maxWidth: '1600px',
|
||||||
|
margin: '0 auto'
|
||||||
|
}}>
|
||||||
|
{!mounted ? (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '50vh',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '20px'
|
||||||
|
}}>
|
||||||
|
<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 === 'tasks' && <TasksBoard tasks={tasks} />}
|
||||||
{activeTab === 'crons' && <CronMonitor crons={crons} />}
|
{activeTab === 'crons' && <CronMonitor crons={crons} />}
|
||||||
|
{activeTab === 'memory' && <MemoryVault memories={memories} />}
|
||||||
{activeTab === 'team' && <Team />}
|
{activeTab === 'team' && <Team />}
|
||||||
{activeTab === 'memory' && <Memory />}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
@@ -87,28 +190,116 @@ export default function MissionControl() {
|
|||||||
|
|
||||||
// ============ TASKS BOARD ============
|
// ============ TASKS BOARD ============
|
||||||
function TasksBoard({ tasks }: { tasks: Task[] }) {
|
function TasksBoard({ tasks }: { tasks: Task[] }) {
|
||||||
const columns: { status: TaskStatus; label: string; color: string }[] = [
|
const columns: { status: TaskStatus; label: string; color: string; bg: string }[] = [
|
||||||
{ status: 'todo', label: 'To Do', color: '#6b7280' },
|
{ status: 'todo', label: 'Queued', color: '#fbbf24', bg: 'rgba(251, 191, 36, 0.1)' },
|
||||||
{ status: 'in-progress', label: 'In Progress', color: '#e94560' },
|
{ status: 'in-progress', label: 'Active', color: '#ff0050', bg: 'rgba(255, 0, 80, 0.1)' },
|
||||||
{ status: 'done', label: 'Done', color: '#10b981' },
|
{ status: 'done', label: 'Complete', color: '#00ff88', bg: 'rgba(0, 255, 136, 0.1)' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '20px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '20px' }}>
|
||||||
{columns.map(col => (
|
{columns.map((col, i) => (
|
||||||
<div key={col.status} style={{ background: '#1a1a2e', borderRadius: '12px', padding: '20px' }}>
|
<div
|
||||||
<h3 style={{ color: col.color, marginBottom: '20px' }}>{col.label}</h3>
|
key={col.status}
|
||||||
{tasks.filter(t => t.status === col.status).map(task => (
|
style={{
|
||||||
<div key={task.id} style={{ background: '#16213e', padding: '15px', borderRadius: '8px', marginBottom: '10px' }}>
|
background: 'rgba(20, 20, 30, 0.6)',
|
||||||
<p>{task.title}</p>
|
borderRadius: '16px',
|
||||||
<span style={{ fontSize: '0.8rem', color: '#9ca3af' }}>@{task.assignee}</span>
|
padding: '25px',
|
||||||
</div>
|
border: '1px solid rgba(255,255,255,0.05)',
|
||||||
))}
|
backdropFilter: 'blur(20px)',
|
||||||
{tasks.filter(t => t.status === col.status).length === 0 && (
|
animation: `fadeSlideIn 0.5s ease ${i * 0.1}s both`
|
||||||
<p style={{ color: '#4b5563', fontSize: '0.9rem' }}>No tasks</p>
|
}}
|
||||||
)}
|
>
|
||||||
|
<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>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<style>{`@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }`}</style>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -116,116 +307,245 @@ function TasksBoard({ tasks }: { tasks: Task[] }) {
|
|||||||
// ============ CRON MONITOR ============
|
// ============ CRON MONITOR ============
|
||||||
function CronMonitor({ crons }: { crons: CronJob[] }) {
|
function CronMonitor({ crons }: { crons: CronJob[] }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ background: '#1a1a2e', borderRadius: '12px', padding: '30px' }}>
|
<div style={{
|
||||||
<h2 style={{ marginBottom: '30px' }}>⏰ Cron Jobs</h2>
|
background: 'rgba(20, 20, 30, 0.6)',
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
borderRadius: '16px',
|
||||||
<thead>
|
padding: '30px',
|
||||||
<tr style={{ borderBottom: '1px solid #2a2a4e' }}>
|
border: '1px solid rgba(255,255,255,0.05)',
|
||||||
<th style={{ textAlign: 'left', padding: '15px', color: '#9ca3af' }}>Job</th>
|
backdropFilter: 'blur(20px)'
|
||||||
<th style={{ textAlign: 'left', padding: '15px', color: '#9ca3af' }}>Status</th>
|
}}>
|
||||||
<th style={{ textAlign: 'left', padding: '15px', color: '#9ca3af' }}>Enabled</th>
|
<div style={{
|
||||||
</tr>
|
display: 'flex',
|
||||||
</thead>
|
alignItems: 'center',
|
||||||
<tbody>
|
gap: '15px',
|
||||||
{crons.map((cron, i) => (
|
marginBottom: '30px'
|
||||||
<tr key={i} style={{ borderBottom: '1px solid #2a2a4e' }}>
|
}}>
|
||||||
<td style={{ padding: '15px' }}>{cron.name}</td>
|
<div style={{
|
||||||
<td style={{ padding: '15px' }}>
|
width: '40px',
|
||||||
<span style={{
|
height: '40px',
|
||||||
background: cron.status === 'ok' ? '#10b981' : '#ef4444',
|
borderRadius: '10px',
|
||||||
padding: '4px 12px', borderRadius: '20px', fontSize: '0.8rem'
|
background: 'linear-gradient(135deg, #00c8ff 0%, #7800ff 100%)',
|
||||||
}}>
|
display: 'flex',
|
||||||
{cron.status}
|
alignItems: 'center',
|
||||||
</span>
|
justifyContent: 'center',
|
||||||
</td>
|
fontSize: '18px'
|
||||||
<td style={{ padding: '15px' }}>
|
}}>
|
||||||
{cron.enabled ? '✅' : '❌'}
|
⏱
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
{crons.length === 0 && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={3} style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>
|
|
||||||
No cron data available
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============ TEAM ============
|
|
||||||
function Team() {
|
|
||||||
const members: TeamMember[] = [
|
|
||||||
{ id: '1', name: 'Jelena', role: 'Chief of Staff', status: 'working' },
|
|
||||||
{ id: '2', name: 'Neo', role: 'CTO / DevOps', status: 'working' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '20px' }}>
|
|
||||||
{members.map(member => (
|
|
||||||
<div key={member.id} style={{ background: '#1a1a2e', borderRadius: '12px', padding: '25px', display: 'flex', alignItems: 'center', gap: '15px' }}>
|
|
||||||
<div style={{
|
|
||||||
width: '50px', height: '50px', borderRadius: '50%',
|
|
||||||
background: member.status === 'working' ? '#10b981' : '#6b7280',
|
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '1.5rem'
|
|
||||||
}}>
|
|
||||||
{member.name[0]}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>{member.name}</h3>
|
|
||||||
<p style={{ color: '#9ca3af', fontSize: '0.9rem' }}>{member.role}</p>
|
|
||||||
<span style={{ color: member.status === 'working' ? '#10b981' : '#6b7280', fontSize: '0.8rem' }}>
|
|
||||||
{member.status === 'working' ? '● Working' : '○ Idle'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div>
|
||||||
|
<h2 style={{ color: '#fff', fontSize: '1.2rem' }}>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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ MEMORY ============
|
// ============ MEMORY VAULT ============
|
||||||
function Memory() {
|
function MemoryVault({ memories }: { memories: Memory[] }) {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
const filtered = memories.filter(m =>
|
||||||
const [memories, setMemories] = useState<Memory[]>([]);
|
m.title.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
useEffect(() => {
|
|
||||||
fetch('/api/data?type=memory')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(setMemories)
|
|
||||||
.catch(() => {});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const filtered = memories.filter(m => m.title.toLowerCase().includes(search.toLowerCase()));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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: '1.2rem' }}>Memory Vault</h2>
|
||||||
|
<p style={{ color: '#666', fontSize: '0.75rem' }}>{memories.length} memories stored</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search memories..."
|
placeholder="Search memories..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
style={{
|
style={{
|
||||||
width: '100%', padding: '15px', borderRadius: '8px', border: 'none',
|
width: '100%',
|
||||||
background: '#1a1a2e', color: 'white', marginBottom: '30px', fontSize: '1rem'
|
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(300px, 1fr))', gap: '20px' }}>
|
|
||||||
{filtered.map(mem => (
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '20px' }}>
|
||||||
<div key={mem.id} style={{ background: '#1a1a2e', borderRadius: '12px', padding: '20px' }}>
|
{filtered.map((mem, i) => (
|
||||||
<h3>{mem.title}</h3>
|
<div
|
||||||
<p style={{ color: '#9ca3af', fontSize: '0.9rem', marginTop: '10px' }}>{mem.preview}</p>
|
key={mem.id}
|
||||||
<span style={{ color: '#6b7280', fontSize: '0.8rem' }}>{mem.date}</span>
|
style={{
|
||||||
|
background: 'rgba(20, 20, 30, 0.6)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
padding: '20px',
|
||||||
|
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>
|
</div>
|
||||||
))}
|
))}
|
||||||
{filtered.length === 0 && (
|
{filtered.length === 0 && (
|
||||||
<p style={{ color: '#6b7280' }}>No memories found</p>
|
<p style={{ color: '#444', gridColumn: '1/-1', textAlign: 'center', padding: '40px' }}>
|
||||||
|
No memories found
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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: '20px' }}>
|
||||||
|
{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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user