Add persistent navbar with SSH Keys link
Refactor App.tsx to use Layout + Outlet pattern with sticky navbar containing: Dashboard | Machines | Sync Pairs | Jobs | SSH Keys | Settings. Clean up redundant button row from Dashboard and inline SSH Keys links.
This commit is contained in:
+58
-8
@@ -1,4 +1,4 @@
|
|||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate, NavLink, Outlet } from 'react-router-dom';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import Login from './pages/Login';
|
import Login from './pages/Login';
|
||||||
import Dashboard from './pages/Dashboard';
|
import Dashboard from './pages/Dashboard';
|
||||||
@@ -20,18 +20,68 @@ function ProtectedRoute({ children }: { children: JSX.Element }) {
|
|||||||
return authed ? children : <Navigate to="/login" />;
|
return authed ? children : <Navigate to="/login" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function NavBar() {
|
||||||
|
const navItems = [
|
||||||
|
{ to: '/', label: 'Dashboard' },
|
||||||
|
{ to: '/machines', label: 'Machines' },
|
||||||
|
{ to: '/sync-pairs', label: 'Sync Pairs' },
|
||||||
|
{ to: '/jobs', label: 'Jobs' },
|
||||||
|
{ to: '/ssh-keys', label: 'SSH Keys' },
|
||||||
|
{ to: '/settings', label: 'Settings' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="sticky top-0 z-50 bg-gray-900 border-b border-gray-700">
|
||||||
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
<div className="flex items-center h-14 gap-1">
|
||||||
|
<span className="text-white font-bold text-lg mr-4">SyncServer</span>
|
||||||
|
{navItems.map(item => (
|
||||||
|
<NavLink
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
end={item.to === '/'}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`px-3 py-1.5 rounded text-sm font-medium transition-colors ${
|
||||||
|
isActive
|
||||||
|
? 'text-white bg-gray-800'
|
||||||
|
: 'text-gray-400 hover:text-white hover:bg-gray-800'
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Layout() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-900">
|
||||||
|
<NavBar />
|
||||||
|
<main>
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
|
<Route element={<ProtectedRoute><Layout /></ProtectedRoute>}>
|
||||||
<Route path="/machines" element={<ProtectedRoute><Machines /></ProtectedRoute>} />
|
<Route path="/" element={<Dashboard />} />
|
||||||
<Route path="/sync-pairs" element={<ProtectedRoute><SyncPairs /></ProtectedRoute>} />
|
<Route path="/machines" element={<Machines />} />
|
||||||
<Route path="/jobs" element={<ProtectedRoute><JobHistory /></ProtectedRoute>} />
|
<Route path="/sync-pairs" element={<SyncPairs />} />
|
||||||
<Route path="/jobs/:id" element={<ProtectedRoute><JobDetail /></ProtectedRoute>} />
|
<Route path="/jobs" element={<JobHistory />} />
|
||||||
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
|
<Route path="/jobs/:id" element={<JobDetail />} />
|
||||||
<Route path="/ssh-keys" element={<ProtectedRoute><SSHKeys /></ProtectedRoute>} />
|
<Route path="/ssh-keys" element={<SSHKeys />} />
|
||||||
|
<Route path="/settings" element={<Settings />} />
|
||||||
|
</Route>
|
||||||
<Route path="*" element={<Navigate to="/" />} />
|
<Route path="*" element={<Navigate to="/" />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { api, Machine, Job } from '../api/client';
|
import { api, Machine, Job } from '../api/client';
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
@@ -66,11 +65,6 @@ export default function Dashboard() {
|
|||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex gap-4">
|
|
||||||
<Link to="/machines" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">Machines</Link>
|
|
||||||
<Link to="/sync-pairs" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">Sync Pairs</Link>
|
|
||||||
<Link to="/jobs" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">All Jobs</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { api, Machine, SSHKey } from '../api/client';
|
import { api, Machine, SSHKey } from '../api/client';
|
||||||
|
|
||||||
export default function Machines() {
|
export default function Machines() {
|
||||||
@@ -77,10 +76,7 @@ export default function Machines() {
|
|||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<div className="flex items-center gap-4">
|
<h1 className="text-2xl font-bold">Machines</h1>
|
||||||
<h1 className="text-2xl font-bold">Machines</h1>
|
|
||||||
<Link to="/ssh-keys" className="text-sm text-blue-400 hover:text-blue-300">Manage SSH Keys</Link>
|
|
||||||
</div>
|
|
||||||
<button onClick={() => setShowForm(true)} className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">
|
<button onClick={() => setShowForm(true)} className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">
|
||||||
Add Machine
|
Add Machine
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const [pubKey, setPubKey] = useState('');
|
const [pubKey, setPubKey] = useState('');
|
||||||
@@ -36,14 +35,6 @@ export default function Settings() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-800 rounded-lg p-4 mb-6">
|
|
||||||
<h2 className="text-lg font-semibold mb-3">SSH Keys</h2>
|
|
||||||
<p className="text-gray-400 text-sm mb-3">
|
|
||||||
Manage SSH key pairs for authenticating to remote machines. Go to the{' '}
|
|
||||||
<Link to="/ssh-keys" className="text-blue-400 hover:text-blue-300">SSH Keys page</Link>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gray-800 rounded-lg p-4">
|
<div className="bg-gray-800 rounded-lg p-4">
|
||||||
<h2 className="text-lg font-semibold mb-3">Quick Reference</h2>
|
<h2 className="text-lg font-semibold mb-3">Quick Reference</h2>
|
||||||
<div className="text-gray-400 text-sm space-y-2">
|
<div className="text-gray-400 text-sm space-y-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user