Обновление файла

This commit is contained in:
2026-07-25 04:27:55 +03:00
parent 23b072a6d3
commit f34850e157
+110 -59
View File
@@ -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>
);