Add hamburger nav menu with 4-page navigation structure
- NavDrawer component: slide-in left drawer with backdrop, matches dark theme - Nav items: Home, Reporting, Knowledge Base, Exports with color-coded icons - Active page highlighted with colored background + indicator dot - Placeholder pages for Reporting (amber), Knowledge Base (green), Exports (purple) - Stats bar and three-column layout conditionally render on Home page only - currentPage state drives all page switching Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus, RefreshCw, Edit2, ChevronDown, Shield, Activity } from 'lucide-react';
|
||||
import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus, RefreshCw, Edit2, ChevronDown, Shield, Activity, Menu } from 'lucide-react';
|
||||
import { useAuth } from './contexts/AuthContext';
|
||||
import LoginForm from './components/LoginForm';
|
||||
import UserMenu from './components/UserMenu';
|
||||
@@ -8,6 +8,10 @@ import AuditLog from './components/AuditLog';
|
||||
import NvdSyncModal from './components/NvdSyncModal';
|
||||
import KnowledgeBaseModal from './components/KnowledgeBaseModal';
|
||||
import KnowledgeBaseViewer from './components/KnowledgeBaseViewer';
|
||||
import NavDrawer from './components/NavDrawer';
|
||||
import ReportingPage from './components/pages/ReportingPage';
|
||||
import KnowledgeBasePage from './components/pages/KnowledgeBasePage';
|
||||
import ExportsPage from './components/pages/ExportsPage';
|
||||
import './App.css';
|
||||
|
||||
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
||||
@@ -171,6 +175,8 @@ export default function App() {
|
||||
const [cveDocuments, setCveDocuments] = useState({});
|
||||
const [quickCheckCVE, setQuickCheckCVE] = useState('');
|
||||
const [quickCheckResult, setQuickCheckResult] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState('home');
|
||||
const [navOpen, setNavOpen] = useState(false);
|
||||
const [showAddCVE, setShowAddCVE] = useState(false);
|
||||
const [showUserManagement, setShowUserManagement] = useState(false);
|
||||
const [showAuditLog, setShowAuditLog] = useState(false);
|
||||
@@ -950,6 +956,12 @@ export default function App() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-intel-darkest grid-bg p-6 relative overflow-hidden fade-in">
|
||||
<NavDrawer
|
||||
isOpen={navOpen}
|
||||
onClose={() => setNavOpen(false)}
|
||||
currentPage={currentPage}
|
||||
onNavigate={setCurrentPage}
|
||||
/>
|
||||
{/* Scanning line effect */}
|
||||
<div className="scan-line"></div>
|
||||
|
||||
@@ -957,12 +969,23 @@ export default function App() {
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<button
|
||||
onClick={() => setNavOpen(true)}
|
||||
style={{ background: 'none', border: '1px solid rgba(14, 165, 233, 0.25)', borderRadius: '0.375rem', padding: '0.5rem', cursor: 'pointer', color: '#64748B', flexShrink: 0 }}
|
||||
onMouseEnter={e => { e.currentTarget.style.color = '#0EA5E9'; e.currentTarget.style.borderColor = 'rgba(14, 165, 233, 0.6)'; }}
|
||||
onMouseLeave={e => { e.currentTarget.style.color = '#64748B'; e.currentTarget.style.borderColor = 'rgba(14, 165, 233, 0.25)'; }}
|
||||
title="Navigation"
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-intel-accent mb-1 font-mono tracking-tight">
|
||||
STEAM Security Dashboard
|
||||
</h1>
|
||||
<p className="text-gray-400 text-sm font-sans">NTS Threat Intelligence and Metric Aggregation</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{canWrite() && (
|
||||
<button
|
||||
@@ -986,8 +1009,8 @@ export default function App() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Bar - Modern refined styling */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
{/* Stats Bar - only shown on Home page */}
|
||||
{currentPage === 'home' && <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div style={STYLES.statCard}>
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: '2px', background: 'linear-gradient(90deg, transparent, #0EA5E9, transparent)', boxShadow: '0 0 8px rgba(14, 165, 233, 0.5)' }}></div>
|
||||
<div style={{ fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.1em', color: '#CBD5E1', marginBottom: '0.25rem' }}>Total CVEs</div>
|
||||
@@ -1008,8 +1031,13 @@ export default function App() {
|
||||
<div style={{ fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.1em', color: '#CBD5E1', marginBottom: '0.25rem' }}>Critical</div>
|
||||
<div style={{ fontSize: '1.5rem', fontWeight: '700', fontFamily: 'monospace', color: '#EF4444', textShadow: '0 0 16px rgba(239, 68, 68, 0.4)' }}>{cves.filter(c => c.severity === 'Critical').length}</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Page content */}
|
||||
{currentPage === 'reporting' && <ReportingPage />}
|
||||
{currentPage === 'knowledge-base' && <KnowledgeBasePage />}
|
||||
{currentPage === 'exports' && <ExportsPage />}
|
||||
|
||||
{/* User Management Modal */}
|
||||
{showUserManagement && (
|
||||
@@ -1624,8 +1652,8 @@ export default function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Three Column Layout */}
|
||||
<div className="grid grid-cols-12 gap-6">
|
||||
{/* Three Column Layout - Home page only */}
|
||||
{currentPage === 'home' && <div className="grid grid-cols-12 gap-6">
|
||||
{/* LEFT PANEL - Wiki/Knowledge Base */}
|
||||
<div className="col-span-12 lg:col-span-3 space-y-4">
|
||||
<div style={{...STYLES.intelCard, padding: '1.5rem', borderLeft: '3px solid #10B981'}} className="rounded-lg">
|
||||
@@ -2472,7 +2500,7 @@ export default function App() {
|
||||
</div>
|
||||
{/* End Right Panel */}
|
||||
|
||||
</div>
|
||||
</div>}
|
||||
{/* End Three Column Layout */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
127
frontend/src/components/NavDrawer.js
Normal file
127
frontend/src/components/NavDrawer.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import React from 'react';
|
||||
import { X, Home, BarChart2, BookOpen, Download } from 'lucide-react';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ id: 'home', label: 'Home', icon: Home, color: '#0EA5E9', description: 'Main dashboard' },
|
||||
{ id: 'reporting', label: 'Reporting', icon: BarChart2,color: '#F59E0B', description: 'Reports & analytics' },
|
||||
{ id: 'knowledge-base', label: 'Knowledge Base', icon: BookOpen, color: '#10B981', description: 'Articles & documentation' },
|
||||
{ id: 'exports', label: 'Exports', icon: Download, color: '#8B5CF6', description: 'Export data & reports' },
|
||||
];
|
||||
|
||||
export default function NavDrawer({ isOpen, onClose, currentPage, onNavigate }) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
onClick={onClose}
|
||||
style={{
|
||||
position: 'fixed', inset: 0,
|
||||
background: 'rgba(0, 0, 0, 0.65)',
|
||||
backdropFilter: 'blur(3px)',
|
||||
zIndex: 50
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Drawer */}
|
||||
<div style={{
|
||||
position: 'fixed', top: 0, left: 0, bottom: 0, width: '280px',
|
||||
background: 'linear-gradient(180deg, #0F1A2E 0%, #0A1628 100%)',
|
||||
borderRight: '1px solid rgba(14, 165, 233, 0.2)',
|
||||
boxShadow: '6px 0 32px rgba(0, 0, 0, 0.7)',
|
||||
zIndex: 51,
|
||||
display: 'flex', flexDirection: 'column',
|
||||
padding: '1.5rem'
|
||||
}}>
|
||||
{/* Drawer header */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '2rem' }}>
|
||||
<div>
|
||||
<div style={{ fontFamily: 'monospace', fontSize: '1rem', fontWeight: '700', color: '#0EA5E9', textTransform: 'uppercase', letterSpacing: '0.15em', textShadow: '0 0 12px rgba(14, 165, 233, 0.5)' }}>
|
||||
STEAM
|
||||
</div>
|
||||
<div style={{ fontFamily: 'monospace', fontSize: '0.65rem', color: '#475569', textTransform: 'uppercase', letterSpacing: '0.1em', marginTop: '2px' }}>
|
||||
Security Dashboard
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{ color: '#475569', padding: '0.25rem', background: 'none', border: 'none', cursor: 'pointer', lineHeight: 1 }}
|
||||
onMouseEnter={e => e.currentTarget.style.color = '#E2E8F0'}
|
||||
onMouseLeave={e => e.currentTarget.style.color = '#475569'}
|
||||
>
|
||||
<X style={{ width: '20px', height: '20px' }} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Nav items */}
|
||||
<nav style={{ display: 'flex', flexDirection: 'column', gap: '0.375rem' }}>
|
||||
{NAV_ITEMS.map(({ id, label, icon: Icon, color, description }) => {
|
||||
const active = currentPage === id;
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => { onNavigate(id); onClose(); }}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: '0.875rem',
|
||||
padding: '0.75rem 0.875rem',
|
||||
borderRadius: '0.5rem',
|
||||
border: active ? `1px solid ${color}50` : '1px solid transparent',
|
||||
background: active ? `${color}18` : 'transparent',
|
||||
cursor: 'pointer', textAlign: 'left', width: '100%',
|
||||
transition: 'background 0.15s, border-color 0.15s'
|
||||
}}
|
||||
onMouseEnter={e => { if (!active) e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; }}
|
||||
onMouseLeave={e => { if (!active) e.currentTarget.style.background = 'transparent'; }}
|
||||
>
|
||||
{/* Icon box */}
|
||||
<div style={{
|
||||
width: '36px', height: '36px', flexShrink: 0,
|
||||
borderRadius: '0.375rem',
|
||||
background: `${color}18`,
|
||||
border: `1px solid ${color}40`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
||||
}}>
|
||||
<Icon style={{ width: '17px', height: '17px', color }} />
|
||||
</div>
|
||||
|
||||
{/* Label + description */}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{
|
||||
fontFamily: 'monospace', fontSize: '0.8rem', fontWeight: '600',
|
||||
color: active ? color : '#CBD5E1',
|
||||
textTransform: 'uppercase', letterSpacing: '0.06em'
|
||||
}}>
|
||||
{label}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.68rem', color: '#475569', marginTop: '1px' }}>
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active indicator dot */}
|
||||
{active && (
|
||||
<div style={{
|
||||
width: '6px', height: '6px', borderRadius: '50%',
|
||||
background: color, boxShadow: `0 0 8px ${color}`, flexShrink: 0
|
||||
}} />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Footer */}
|
||||
<div style={{
|
||||
marginTop: 'auto', paddingTop: '1rem',
|
||||
borderTop: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<div style={{ fontSize: '0.6rem', color: '#1E293B', fontFamily: 'monospace', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
|
||||
NTS Threat Intelligence
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
25
frontend/src/components/pages/ExportsPage.js
Normal file
25
frontend/src/components/pages/ExportsPage.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
export default function ExportsPage() {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '60vh' }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{
|
||||
width: '72px', height: '72px', borderRadius: '1rem', margin: '0 auto 1.5rem',
|
||||
background: 'rgba(139, 92, 246, 0.1)',
|
||||
border: '1px solid rgba(139, 92, 246, 0.3)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
||||
}}>
|
||||
<Download style={{ width: '36px', height: '36px', color: '#8B5CF6' }} />
|
||||
</div>
|
||||
<h2 style={{ fontFamily: 'monospace', fontSize: '1.5rem', fontWeight: '700', color: '#8B5CF6', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '0.5rem' }}>
|
||||
Exports
|
||||
</h2>
|
||||
<p style={{ color: '#475569', fontSize: '0.875rem', fontFamily: 'monospace' }}>
|
||||
Under construction — coming soon
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
frontend/src/components/pages/KnowledgeBasePage.js
Normal file
25
frontend/src/components/pages/KnowledgeBasePage.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { BookOpen } from 'lucide-react';
|
||||
|
||||
export default function KnowledgeBasePage() {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '60vh' }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{
|
||||
width: '72px', height: '72px', borderRadius: '1rem', margin: '0 auto 1.5rem',
|
||||
background: 'rgba(16, 185, 129, 0.1)',
|
||||
border: '1px solid rgba(16, 185, 129, 0.3)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
||||
}}>
|
||||
<BookOpen style={{ width: '36px', height: '36px', color: '#10B981' }} />
|
||||
</div>
|
||||
<h2 style={{ fontFamily: 'monospace', fontSize: '1.5rem', fontWeight: '700', color: '#10B981', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '0.5rem' }}>
|
||||
Knowledge Base
|
||||
</h2>
|
||||
<p style={{ color: '#475569', fontSize: '0.875rem', fontFamily: 'monospace' }}>
|
||||
Under construction — coming soon
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
frontend/src/components/pages/ReportingPage.js
Normal file
25
frontend/src/components/pages/ReportingPage.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { BarChart2 } from 'lucide-react';
|
||||
|
||||
export default function ReportingPage() {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '60vh' }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{
|
||||
width: '72px', height: '72px', borderRadius: '1rem', margin: '0 auto 1.5rem',
|
||||
background: 'rgba(245, 158, 11, 0.1)',
|
||||
border: '1px solid rgba(245, 158, 11, 0.3)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
||||
}}>
|
||||
<BarChart2 style={{ width: '36px', height: '36px', color: '#F59E0B' }} />
|
||||
</div>
|
||||
<h2 style={{ fontFamily: 'monospace', fontSize: '1.5rem', fontWeight: '700', color: '#F59E0B', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '0.5rem' }}>
|
||||
Reporting
|
||||
</h2>
|
||||
<p style={{ color: '#475569', fontSize: '0.875rem', fontFamily: 'monospace' }}>
|
||||
Under construction — coming soon
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user