feat: Revamp navigation structure in App component with dropdown menus and enhanced styling for improved user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 4m49s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 4m49s
This commit is contained in:
@@ -6,6 +6,190 @@
|
|||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23e9ecef"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
|
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23e9ecef"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Navbar styles */
|
||||||
|
.navbar {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item.dropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: var(--tblr-body-color);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link:hover {
|
||||||
|
color: var(--tblr-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link.active {
|
||||||
|
color: var(--tblr-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link .icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-toggle::after {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
min-width: 10rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--tblr-body-color);
|
||||||
|
text-align: left;
|
||||||
|
list-style: none;
|
||||||
|
background-color: var(--tblr-bg-surface);
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid var(--tblr-border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-menu.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-menu-columns {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
min-width: 16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-menu-column {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
clear: both;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--tblr-body-color);
|
||||||
|
text-align: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-item:hover {
|
||||||
|
color: var(--tblr-primary);
|
||||||
|
background-color: var(--tblr-bg-surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-item.active {
|
||||||
|
color: var(--tblr-primary);
|
||||||
|
background-color: var(--tblr-primary-lt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-item .icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar brand */
|
||||||
|
.navbar-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 0.3125rem;
|
||||||
|
padding-bottom: 0.3125rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--tblr-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand:hover {
|
||||||
|
color: var(--tblr-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand-icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User menu */
|
||||||
|
.navbar-nav .nav-item.dropdown .nav-link {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item.dropdown .avatar {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badge styles */
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.35em 0.65em;
|
||||||
|
font-size: 0.75em;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: baseline;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-red {
|
||||||
|
background-color: #d63939 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive navbar */
|
||||||
|
@media (max-width: 991.98px) {
|
||||||
|
.navbar-collapse {
|
||||||
|
flex-basis: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-menu {
|
||||||
|
position: static;
|
||||||
|
float: none;
|
||||||
|
width: auto;
|
||||||
|
margin-top: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Custom animations */
|
/* Custom animations */
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
|
|||||||
+194
-33
@@ -19,7 +19,12 @@ import {
|
|||||||
IconServer,
|
IconServer,
|
||||||
IconFilter,
|
IconFilter,
|
||||||
IconDownload,
|
IconDownload,
|
||||||
IconCreditCard
|
IconCreditCard,
|
||||||
|
IconMoon,
|
||||||
|
IconGrid,
|
||||||
|
IconHeart,
|
||||||
|
IconCode,
|
||||||
|
IconGear
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import DataManager from './DataManager';
|
import DataManager from './DataManager';
|
||||||
import ServerManager from './ServerManager';
|
import ServerManager from './ServerManager';
|
||||||
@@ -55,46 +60,202 @@ function MainLayout() {
|
|||||||
{ id: 2, title: 'Изменения сохранены', message: 'Список AS обновлен в S3', time: '5 мин назад' }
|
{ id: 2, title: 'Изменения сохранены', message: 'Список AS обновлен в S3', time: '5 мин назад' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const navItems = [
|
// Состояние для управления выпадающими меню
|
||||||
{ id: 'domains', title: 'Домены', icon: IconWorld, path: '/domains' },
|
const [dropdownStates, setDropdownStates] = useState({
|
||||||
{ id: 'domains-new', title: 'Домены New', icon: IconWorld, path: '/domains-new' },
|
data: false,
|
||||||
{ id: 'ip-ranges', title: 'IP-диапазоны', icon: IconNetwork, path: '/ip-ranges' },
|
management: false,
|
||||||
{ id: 'asns', title: 'AS', icon: IconNetwork, path: '/asns' },
|
tools: false,
|
||||||
{ id: 'auto-urls', title: 'Авто URL', icon: IconDownload, path: '/auto-urls' },
|
settings: false
|
||||||
{ id: 'servers', title: 'Серверы', icon: IconServer, path: '/servers' },
|
});
|
||||||
{ id: 'billing', title: 'Биллинг', icon: IconCreditCard, path: '/billing' },
|
|
||||||
{ id: 'filters', title: 'Фильтры', icon: IconFilter, path: '/filters' },
|
const toggleDropdown = (dropdown) => {
|
||||||
{ id: 'files', title: 'Файлы', icon: IconFileText, path: '/files' },
|
setDropdownStates(prev => ({
|
||||||
{ id: 'cloud', title: 'Облако', icon: IconCloud, path: '/cloud' },
|
...prev,
|
||||||
{ id: 'security', title: 'Безопасность', icon: IconShield, path: '/security' },
|
[dropdown]: !prev[dropdown]
|
||||||
{ id: 'activity', title: 'Активность', icon: IconActivity, path: '/activity' },
|
}));
|
||||||
{ id: 'settings', title: 'Настройки', icon: IconSettings, path: '/settings' }
|
};
|
||||||
|
|
||||||
|
const navCategories = [
|
||||||
|
{
|
||||||
|
id: 'home',
|
||||||
|
title: 'Главная',
|
||||||
|
icon: IconHome,
|
||||||
|
path: '/',
|
||||||
|
single: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'data',
|
||||||
|
title: 'Данные',
|
||||||
|
icon: IconDatabase,
|
||||||
|
items: [
|
||||||
|
{ id: 'domains', title: 'Домены', path: '/domains', icon: IconWorld },
|
||||||
|
{ id: 'domains-new', title: 'Домены New', path: '/domains-new', icon: IconWorld },
|
||||||
|
{ id: 'ip-ranges', title: 'IP-диапазоны', path: '/ip-ranges', icon: IconNetwork },
|
||||||
|
{ id: 'asns', title: 'AS', path: '/asns', icon: IconNetwork }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'management',
|
||||||
|
title: 'Управление',
|
||||||
|
icon: IconServer,
|
||||||
|
items: [
|
||||||
|
{ id: 'servers', title: 'Серверы', path: '/servers', icon: IconServer },
|
||||||
|
{ id: 'filters', title: 'Фильтры', path: '/filters', icon: IconFilter },
|
||||||
|
{ id: 'billing', title: 'Биллинг', path: '/billing', icon: IconCreditCard }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tools',
|
||||||
|
title: 'Инструменты',
|
||||||
|
icon: IconSettings,
|
||||||
|
items: [
|
||||||
|
{ id: 'auto-urls', title: 'Авто URL', path: '/auto-urls', icon: IconDownload },
|
||||||
|
{ id: 'files', title: 'Файлы', path: '/files', icon: IconFileText },
|
||||||
|
{ id: 'cloud', title: 'Облако', path: '/cloud', icon: IconCloud },
|
||||||
|
{ id: 'security', title: 'Безопасность', path: '/security', icon: IconShield },
|
||||||
|
{ id: 'activity', title: 'Активность', path: '/activity', icon: IconActivity }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
title: 'Настройки',
|
||||||
|
icon: IconSettings,
|
||||||
|
path: '/settings',
|
||||||
|
single: true
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Определяем активную вкладку по адресу
|
// Определяем активную вкладку по адресу
|
||||||
const activeTab = navItems.find(item => location.pathname.startsWith(item.path))?.id || 'domains';
|
const getActiveTab = () => {
|
||||||
|
for (const category of navCategories) {
|
||||||
|
if (category.single && location.pathname === category.path) {
|
||||||
|
return category.id;
|
||||||
|
}
|
||||||
|
if (category.items) {
|
||||||
|
for (const item of category.items) {
|
||||||
|
if (location.pathname.startsWith(item.path)) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'home';
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeTab = getActiveTab();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
{/* Верхнее меню Tabler Admin */}
|
{/* Верхнее меню Tabler Admin */}
|
||||||
<header className="navbar navbar-expand-md navbar-light d-print-none" style={{boxShadow: '0 1px 0 0 #e9ecef'}}>
|
<header className="navbar navbar-expand-md navbar-light d-print-none" style={{boxShadow: '0 1px 0 0 #e9ecef'}}>
|
||||||
<div className="container-xl d-flex align-items-center justify-content-between">
|
<div className="container-xl">
|
||||||
<Link to="/" className="navbar-brand d-flex align-items-center">
|
{/* Логотип */}
|
||||||
<IconBrandTabler className="navbar-brand-icon me-2" />
|
<div className="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||||
<span className="fw-bold">S3 Lists Manager</span>
|
<Link to="/" className="navbar-brand d-flex align-items-center">
|
||||||
</Link>
|
<IconBrandTabler className="navbar-brand-icon me-2" />
|
||||||
<nav className="nav nav-tabs ms-4">
|
<span className="fw-bold">S3 Lists Manager</span>
|
||||||
{navItems.map(item => (
|
</Link>
|
||||||
<Link
|
</div>
|
||||||
key={item.id}
|
|
||||||
to={item.path}
|
{/* Основное меню */}
|
||||||
className={`nav-link${activeTab === item.id ? ' active fw-bold' : ''}`}
|
<div className="collapse navbar-collapse" id="navbar-menu">
|
||||||
style={{display: 'flex', alignItems: 'center'}}>
|
<div className="d-flex flex-column flex-md-row flex-fill align-items-stretch align-items-md-center">
|
||||||
<item.icon className="me-2" />
|
<ul className="navbar-nav">
|
||||||
{item.title}
|
{navCategories.map(category => (
|
||||||
</Link>
|
<li key={category.id} className="nav-item dropdown">
|
||||||
))}
|
{category.single ? (
|
||||||
</nav>
|
<Link
|
||||||
|
to={category.path}
|
||||||
|
className={`nav-link${activeTab === category.id ? ' active' : ''}`}
|
||||||
|
>
|
||||||
|
<category.icon className="icon icon-tabler" />
|
||||||
|
<span className="nav-link-title">{category.title}</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
className={`nav-link dropdown-toggle${activeTab.startsWith(category.id) ? ' active' : ''}`}
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleDropdown(category.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<category.icon className="icon icon-tabler" />
|
||||||
|
<span className="nav-link-title">{category.title}</span>
|
||||||
|
</a>
|
||||||
|
{dropdownStates[category.id] && (
|
||||||
|
<div className="dropdown-menu show">
|
||||||
|
<div className="dropdown-menu-columns">
|
||||||
|
<div className="dropdown-menu-column">
|
||||||
|
{category.items.map(item => (
|
||||||
|
<Link
|
||||||
|
key={item.id}
|
||||||
|
to={item.path}
|
||||||
|
className={`dropdown-item${activeTab === item.id ? ' active' : ''}`}
|
||||||
|
>
|
||||||
|
<item.icon className="icon icon-tabler" />
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Правая часть навбара */}
|
||||||
|
<div className="navbar-nav flex-row order-md-last">
|
||||||
|
{/* Иконки */}
|
||||||
|
<div className="nav-item dropdown">
|
||||||
|
<a href="#" className="nav-link d-flex lh-1 text-reset p-0 ms-3" aria-label="Source code">
|
||||||
|
<IconCode className="icon icon-tabler" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="nav-item dropdown">
|
||||||
|
<a href="#" className="nav-link d-flex lh-1 text-reset p-0 ms-3" aria-label="Sponsor">
|
||||||
|
<IconHeart className="icon icon-tabler" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="nav-item dropdown">
|
||||||
|
<a href="#" className="nav-link d-flex lh-1 text-reset p-0 ms-3" aria-label="Dark mode">
|
||||||
|
<IconMoon className="icon icon-tabler" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="nav-item dropdown">
|
||||||
|
<a href="#" className="nav-link d-flex lh-1 text-reset p-0 ms-3" aria-label="Notifications">
|
||||||
|
<IconBell className="icon icon-tabler" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="nav-item dropdown">
|
||||||
|
<a href="#" className="nav-link d-flex lh-1 text-reset p-0 ms-3" aria-label="Apps">
|
||||||
|
<IconGrid className="icon icon-tabler" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="nav-item dropdown">
|
||||||
|
<a href="#" className="nav-link d-flex lh-1 text-reset p-0 ms-3" aria-label="Theme settings">
|
||||||
|
<IconGear className="icon icon-tabler" />
|
||||||
|
<span className="badge bg-red"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Пользователь */}
|
||||||
|
<div className="nav-item dropdown">
|
||||||
|
<a href="#" className="nav-link d-flex lh-1 text-reset p-0 ms-3" aria-label="User menu">
|
||||||
|
<span className="avatar avatar-sm" style={{backgroundImage: 'url(./static/avatars/000m.jpg)'}}></span>
|
||||||
|
<div className="d-none d-xl-block ps-2">
|
||||||
|
<div>Admin User</div>
|
||||||
|
<div className="mt-1 small text-muted">Administrator</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="container-xl mt-4">
|
<div className="container-xl mt-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user