Обновление файла
This commit is contained in:
+110
-59
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState, createContext, useContext } from "react";
|
||||
import { Routes, Route, Navigate, Link, useNavigate } from "react-router-dom";
|
||||
import { Routes, Route, Navigate, NavLink, useNavigate, Link } from "react-router-dom";
|
||||
import { api } from "./api.js";
|
||||
import Login from "./pages/Login.jsx";
|
||||
import Register from "./pages/Register.jsx";
|
||||
@@ -38,34 +38,83 @@ function Protected({ user, loading, children }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
function Topbar() {
|
||||
// Иконки (inline SVG — без зависимостей).
|
||||
const Icon = ({ d, size = 18 }) => (
|
||||
<svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor"
|
||||
strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d={d} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const Icons = {
|
||||
server: "M5 4h14a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zM5 14h14a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1zM7 6h.01M7 16h.01",
|
||||
grid: "M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z",
|
||||
users: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75",
|
||||
logout: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9",
|
||||
layers: "M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5",
|
||||
};
|
||||
|
||||
function Sidebar() {
|
||||
const { user, setUser } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
const navLinkClass = ({ isActive }) =>
|
||||
"nav-link" + (isActive ? " active" : "");
|
||||
|
||||
return (
|
||||
<div className="topbar">
|
||||
<div className="brand">
|
||||
<span className="prompt">$</span> vps-panel
|
||||
<aside className="sidebar">
|
||||
<div className="sidebar-brand">
|
||||
<div className="sidebar-brand-mark">VP</div>
|
||||
<div className="sidebar-brand-text">VPS Panel</div>
|
||||
</div>
|
||||
{user && (
|
||||
<div className="topbar-right">
|
||||
<Link to="/">Мои VPS</Link>
|
||||
{user.role === "admin" && <Link to="/admin/templates">Шаблоны</Link>}
|
||||
{user.role === "admin" && <Link to="/admin/users">Пользователи</Link>}
|
||||
<span className="badge">{user.email}</span>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => {
|
||||
api.logout();
|
||||
setUser(null);
|
||||
navigate("/login");
|
||||
}}
|
||||
>
|
||||
Выйти
|
||||
</button>
|
||||
|
||||
<nav className="sidebar-nav">
|
||||
<NavLink to="/" end className={navLinkClass}>
|
||||
<Icon d={Icons.server} />
|
||||
<span>Мои VPS</span>
|
||||
</NavLink>
|
||||
{user.role === "admin" && (
|
||||
<>
|
||||
<NavLink to="/admin/templates" className={navLinkClass}>
|
||||
<Icon d={Icons.layers} />
|
||||
<span>Шаблоны</span>
|
||||
</NavLink>
|
||||
<NavLink to="/admin/users" className={navLinkClass}>
|
||||
<Icon d={Icons.users} />
|
||||
<span>Пользователи</span>
|
||||
</NavLink>
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<div className="sidebar-footer">
|
||||
<div className="sidebar-user">
|
||||
<div className="sidebar-user-avatar">
|
||||
{user.email[0].toUpperCase()}
|
||||
</div>
|
||||
<div className="sidebar-user-info">
|
||||
<div className="sidebar-user-email">{user.email}</div>
|
||||
<div className="sidebar-user-role">
|
||||
{user.role === "admin" ? "Администратор" : "Клиент"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-sm btn-logout"
|
||||
onClick={() => {
|
||||
api.logout();
|
||||
setUser(null);
|
||||
navigate("/login");
|
||||
}}
|
||||
title="Выйти"
|
||||
>
|
||||
<Icon d={Icons.logout} size={16} />
|
||||
<span>Выйти</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,43 +124,45 @@ export default function App() {
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, setUser }}>
|
||||
<div className="shell">
|
||||
<Topbar />
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<Protected user={user} loading={loading}>
|
||||
<Dashboard />
|
||||
{user && <Sidebar />}
|
||||
<main className="main">
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<Protected user={user} loading={loading}>
|
||||
<Dashboard />
|
||||
</Protected>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/instances/:id"
|
||||
element={
|
||||
<Protected user={user} loading={loading}>
|
||||
<InstanceDetail />
|
||||
</Protected>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/templates"
|
||||
element={
|
||||
<Protected user={user} loading={loading}>
|
||||
<AdminTemplates />
|
||||
</Protected>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/users"
|
||||
element={
|
||||
<Protected user={user} loading={loading}>
|
||||
<AdminUsers />
|
||||
</Protected>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/instances/:id"
|
||||
element={
|
||||
<Protected user={user} loading={loading}>
|
||||
<InstanceDetail />
|
||||
</Protected>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/templates"
|
||||
element={
|
||||
<Protected user={user} loading={loading}>
|
||||
<AdminTemplates />
|
||||
</Protected>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/users"
|
||||
element={
|
||||
<Protected user={user} loading={loading}>
|
||||
<AdminUsers />
|
||||
</Protected>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user