refactor(PageHeader): standardize header component across various managers by adding icons for improved visual consistency and user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m53s

This commit is contained in:
2026-02-16 19:26:56 +07:00
parent 9784ec581e
commit cf6d8b61a3
14 changed files with 290 additions and 316 deletions
+1
View File
@@ -529,6 +529,7 @@ function ASNsNewManager() {
<PageHeader <PageHeader
title="ASNs" title="ASNs"
icon={<IconHash size={24} />}
meta={<LastSaved timestamp={lastModified} variant="compact" />} meta={<LastSaved timestamp={lastModified} variant="compact" />}
actions={ actions={
<PageHeaderActions <PageHeaderActions
+1
View File
@@ -276,6 +276,7 @@ function AutoUrlManager() {
<div> <div>
<PageHeader <PageHeader
title="Автоматические URL" title="Автоматические URL"
icon={<IconLink size={24} />}
actions={( actions={(
<div className="d-flex align-items-center justify-content-between flex-wrap gap-2"> <div className="d-flex align-items-center justify-content-between flex-wrap gap-2">
<div className="btn-list"> <div className="btn-list">
+1
View File
@@ -576,6 +576,7 @@ function BillingManager() {
{/* Заголовок */} {/* Заголовок */}
<PageHeader <PageHeader
title="Биллинг" title="Биллинг"
icon={<IconCreditCard size={24} />}
actions={ actions={
<PageHeaderActions <PageHeaderActions
loading={loading} loading={loading}
+6 -14
View File
@@ -350,16 +350,10 @@ function CommunitiesManager() {
return ( return (
<div className="page-wrapper"> <div className="page-wrapper">
<div className="page-header d-print-none"> <PageHeader
<div className="container-xl"> title="Справочник Community"
<div className="row g-2 align-items-center"> icon={<IconFilter size={24} />}
<div className="col"> actions={
<h2 className="page-title">
<IconFilter className="icon me-2" />
Справочник Community
</h2>
</div>
<div className="col-auto ms-auto d-print-none">
<PageHeaderActions <PageHeaderActions
loading={loading} loading={loading}
onRefresh={fetchItems} onRefresh={fetchItems}
@@ -370,10 +364,8 @@ function CommunitiesManager() {
onSave={handleSave} onSave={handleSave}
disableSave={loading} disableSave={loading}
/> />
</div> }
</div> />
</div>
</div>
<div className="page-body"> <div className="page-body">
<div className="container-xl"> <div className="container-xl">
+1
View File
@@ -205,6 +205,7 @@ function Dashboard() {
{/* Заголовок страницы */} {/* Заголовок страницы */}
<PageHeader <PageHeader
title="Панель" title="Панель"
icon={<IconCloud size={24} />}
actions={( actions={(
<div className="d-flex align-items-center gap-3"> <div className="d-flex align-items-center gap-3">
{lastFetchTime && ( {lastFetchTime && (
+1
View File
@@ -518,6 +518,7 @@ function DomainsNewManager() {
<PageHeader <PageHeader
title="Домены" title="Домены"
icon={<IconWorld size={24} />}
meta={<LastSaved timestamp={lastModified} variant="compact" />} meta={<LastSaved timestamp={lastModified} variant="compact" />}
actions={( actions={(
<PageHeaderActions <PageHeaderActions
+13 -27
View File
@@ -17,6 +17,7 @@ import {
IconChevronDown, IconChevronDown,
IconChevronUp IconChevronUp
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import PageHeader from './components/PageHeader.jsx';
// Функция для получения флага по коду страны // Функция для получения флага по коду страны
const countryToFlag = (code) => { const countryToFlag = (code) => {
@@ -495,17 +496,11 @@ function EasySwitchManager() {
if (loading && servers.length === 0) { if (loading && servers.length === 0) {
return ( return (
<div className="container-xl"> <div className="container-xl">
<div className="page-header"> <PageHeader
<div className="row align-items-center"> title="Easy Switch"
<div className="col"> icon={<IconWorld size={24} />}
<h2 className="page-title"> meta="Быстрое переключение шлюзов"
<IconWorld className="me-2" /> />
Easy Switch
</h2>
<div className="text-muted mt-1">Быстрое переключение шлюзов</div>
</div>
</div>
</div>
<div className="card"> <div className="card">
<div className="card-body text-center py-5"> <div className="card-body text-center py-5">
@@ -521,19 +516,11 @@ function EasySwitchManager() {
return ( return (
<div className="container-xl"> <div className="container-xl">
{/* Заголовок */} <PageHeader
<div className="page-header mb-3"> title="Easy Switch"
<div className="row align-items-center"> icon={<IconWorld size={24} />}
<div className="col"> meta={`Быстрое переключение шлюзов • ${servers.length} ${servers.length === 1 ? 'сервер' : 'серверов'} • ${communitiesDirectory.length} communities`}
<h2 className="page-title"> actions={
<IconWorld className="me-2" />
Easy Switch
</h2>
<div className="text-muted mt-1">
Быстрое переключение шлюзов • {servers.length} {servers.length === 1 ? 'сервер' : 'серверов'} • {communitiesDirectory.length} communities
</div>
</div>
<div className="col-auto">
<div className="btn-list"> <div className="btn-list">
{hasChanges && ( {hasChanges && (
<button <button
@@ -553,9 +540,8 @@ function EasySwitchManager() {
Обновить Обновить
</button> </button>
</div> </div>
</div> }
</div> />
</div>
{/* Вкладки */} {/* Вкладки */}
<div className="mb-3"> <div className="mb-3">
+7 -13
View File
@@ -6,6 +6,7 @@ import FormModal from './components/FormModal.jsx';
import ConfirmModal from './components/ConfirmModal.jsx'; import ConfirmModal from './components/ConfirmModal.jsx';
import FormField from './components/FormField.jsx'; import FormField from './components/FormField.jsx';
import Modal from './components/Modal.jsx'; import Modal from './components/Modal.jsx';
import PageHeader from './components/PageHeader.jsx';
import { import {
IconPlus, IconPlus,
IconSearch, IconSearch,
@@ -1755,15 +1756,11 @@ function FilterManager() {
return ( return (
<div className="page-wrapper"> <div className="page-wrapper">
<div className="page-header d-print-none"> <PageHeader
<div className="container-xl"> title="Управление фильтрами frouting"
<div className="row g-2 align-items-center"> icon={<IconFilter size={24} />}
<div className="col"> actions={
<h2 className="page-title">Управление фильтрами frouting</h2>
</div>
<div className="col-auto ms-auto d-print-none">
<div className="btn-list"> <div className="btn-list">
{/* Dropdown выбора сервера */}
<div className="dropdown"> <div className="dropdown">
<button <button
className="btn btn-primary dropdown-toggle" className="btn btn-primary dropdown-toggle"
@@ -1839,7 +1836,6 @@ function FilterManager() {
</button> </button>
</div> </div>
</div> </div>
{selectedServer && ( {selectedServer && (
<> <>
<button <button
@@ -1878,10 +1874,8 @@ function FilterManager() {
</> </>
)} )}
</div> </div>
</div> }
</div> />
</div>
</div>
<div className="page-body"> <div className="page-body">
<div className="container-xl"> <div className="container-xl">
+1
View File
@@ -790,6 +790,7 @@ function IPRangesManager() {
<PageHeader <PageHeader
title="IP-диапазоны" title="IP-диапазоны"
icon={<IconNetwork size={24} />}
meta={<LastSaved timestamp={lastModified} variant="compact" />} meta={<LastSaved timestamp={lastModified} variant="compact" />}
actions={ actions={
<PageHeaderActions <PageHeaderActions
+1
View File
@@ -550,6 +550,7 @@ function MikrotikBackupsManager() {
<div className="page"> <div className="page">
<PageHeader <PageHeader
title="MikroTik Backups" title="MikroTik Backups"
icon={<IconDownload size={24} />}
meta={`Текущий сервер: ${currentServerLabel}`} meta={`Текущий сервер: ${currentServerLabel}`}
actions={actions} actions={actions}
/> />
+1
View File
@@ -227,6 +227,7 @@ function MikrotikTools() {
<div className="page"> <div className="page">
<PageHeader <PageHeader
title="MikroTik Tools" title="MikroTik Tools"
icon={<IconRoute size={24} />}
meta={`Текущий сервер: ${currentServerLabel}`} meta={`Текущий сервер: ${currentServerLabel}`}
/> />
+7 -15
View File
@@ -4,6 +4,7 @@ import { useNotify } from './components/NotifyProvider.jsx';
import FormModal from './components/FormModal.jsx'; import FormModal from './components/FormModal.jsx';
import FormField from './components/FormField.jsx'; import FormField from './components/FormField.jsx';
import ConfirmModal from './components/ConfirmModal.jsx'; import ConfirmModal from './components/ConfirmModal.jsx';
import PageHeader from './components/PageHeader.jsx';
import ServerAutocompleteInput from './components/ServerAutocompleteInput.jsx'; import ServerAutocompleteInput from './components/ServerAutocompleteInput.jsx';
import GatewayAutocompleteInput from './components/GatewayAutocompleteInput.jsx'; import GatewayAutocompleteInput from './components/GatewayAutocompleteInput.jsx';
import Tooltip from './components/Tooltip.jsx'; import Tooltip from './components/Tooltip.jsx';
@@ -3231,18 +3232,11 @@ function NetworkConfigManager() {
return ( return (
<div className="network-config-manager"> <div className="network-config-manager">
{/* Page Header */} <PageHeader
<div className="page-header d-print-none mb-4"> title="Сетевые настройки"
<div className="row align-items-center"> icon={<IconNetwork size={24} />}
<div className="col"> actions={
<h2 className="page-title d-flex align-items-center">
<IconNetwork className="me-2" size={28} />
Сетевые настройки
</h2>
</div>
<div className="col-auto ms-auto d-print-none">
<div className="btn-list"> <div className="btn-list">
{/* Tabs as button group */}
<div className="btn-group me-2" role="group"> <div className="btn-group me-2" role="group">
<button <button
className={`btn ${activeTab === 'gateways' ? 'btn-primary' : 'btn-outline-primary'}`} className={`btn ${activeTab === 'gateways' ? 'btn-primary' : 'btn-outline-primary'}`}
@@ -3290,7 +3284,6 @@ function NetworkConfigManager() {
)} )}
</button> </button>
</div> </div>
<button <button
type="button" type="button"
className="btn btn-outline-secondary" className="btn btn-outline-secondary"
@@ -3322,9 +3315,8 @@ function NetworkConfigManager() {
Сохранить Сохранить
</button> </button>
</div> </div>
</div> }
</div> />
</div>
{/* Toolbar */} {/* Toolbar */}
<div className="card mb-3"> <div className="card mb-3">
+7 -16
View File
@@ -33,6 +33,7 @@ import {
ServerCard, ServerCard,
ServerBulkActions ServerBulkActions
} from './components/server/index.js'; } from './components/server/index.js';
import PageHeader from './components/PageHeader.jsx';
const API_URL = '/api'; const API_URL = '/api';
@@ -585,18 +586,11 @@ function ServerManager() {
return ( return (
<div className="server-manager-layout"> <div className="server-manager-layout">
{/* Header */} <PageHeader
<div className="page-header d-print-none mb-3"> title="Управление серверами"
<div className="row align-items-center"> icon={<IconServer size={24} />}
<div className="col"> actions={
<h2 className="page-title d-flex align-items-center">
<IconServer className="me-2" />
Управление серверами
</h2>
</div>
<div className="col-auto ms-auto d-print-none">
<div className="btn-list"> <div className="btn-list">
{/* Tabs */}
<div className="btn-group me-2" role="group"> <div className="btn-group me-2" role="group">
<button <button
className={`btn ${activeTab === 'servers' ? 'btn-primary' : 'btn-outline-primary'}`} className={`btn ${activeTab === 'servers' ? 'btn-primary' : 'btn-outline-primary'}`}
@@ -613,8 +607,6 @@ function ServerManager() {
Связи Связи
</button> </button>
</div> </div>
{/* Actions */}
<button type="button" className="btn btn-outline-secondary" onClick={exportAllLinks} disabled={servers.length === 0}> <button type="button" className="btn btn-outline-secondary" onClick={exportAllLinks} disabled={servers.length === 0}>
<IconDownload size={16} className="me-1" /> Экспорт <IconDownload size={16} className="me-1" /> Экспорт
</button> </button>
@@ -625,9 +617,8 @@ function ServerManager() {
<IconPlus size={16} className="me-1" /> Добавить <IconPlus size={16} className="me-1" /> Добавить
</button> </button>
</div> </div>
</div> }
</div> />
</div>
{/* Offcanvas: фильтры (только для вкладки «Серверы») */} {/* Offcanvas: фильтры (только для вкладки «Серверы») */}
{activeTab === 'servers' && ( {activeTab === 'servers' && (
+14 -3
View File
@@ -1,7 +1,15 @@
import React from 'react' import React from 'react'
/** Заголовок страницы в стиле Tabler (page-header + page-title + опционально actions справа) */ /**
function PageHeader({ title, pretitle, actions, meta }) { * Единый заголовок страницы в стиле Tabler UI.
* Используется на всех страницах для одинакового UX.
* @param {string} title - Заголовок страницы
* @param {React.ReactNode} [icon] - Иконка слева от заголовка (например, <IconServer />)
* @param {string} [pretitle] - Небольшая надпись над заголовком (page-pretitle)
* @param {React.ReactNode} [meta] - Подзаголовок или мета-информация под заголовком
* @param {React.ReactNode} [actions] - Кнопки/действия справа (btn-list, btn-group)
*/
function PageHeader({ title, icon, pretitle, actions, meta }) {
return ( return (
<div className="page-header d-print-none mb-4"> <div className="page-header d-print-none mb-4">
<div className="row align-items-center"> <div className="row align-items-center">
@@ -9,7 +17,10 @@ function PageHeader({ title, pretitle, actions, meta }) {
{pretitle && ( {pretitle && (
<div className="page-pretitle">{pretitle}</div> <div className="page-pretitle">{pretitle}</div>
)} )}
<h1 className="page-title">{title}</h1> <h1 className="page-title d-flex align-items-center">
{icon && <span className="me-2 d-flex align-items-center">{icon}</span>}
{title}
</h1>
{meta && ( {meta && (
<div className="text-muted small mt-1">{meta}</div> <div className="text-muted small mt-1">{meta}</div>
)} )}