refactor(NetworkConfigManager): improve layout and styling of server cards for better user experience and readability
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m47s

This commit is contained in:
2026-01-25 14:08:30 +07:00
parent 3b858441a2
commit 4feb84338a
+105 -112
View File
@@ -2242,123 +2242,116 @@ function NetworkConfigManager() {
const isUnassigned = serverId === '__unassigned__';
return (
<div key={serverId} className="mb-4">
<div key={serverId} className="card mb-3">
{/* Server header - улучшенная структура с четкой иерархией */}
<div
className="card mb-3"
style={{
borderLeft: '4px solid rgba(32, 107, 196, 0.3)',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.08)'
className="card-header py-3 px-4"
style={{
backgroundColor: 'rgba(32, 107, 196, 0.06)',
borderBottom: '1px solid rgba(32, 107, 196, 0.15)',
borderLeft: '4px solid rgba(32, 107, 196, 0.3)'
}}
>
<div
className="card-header py-3 px-4"
style={{
backgroundColor: 'rgba(32, 107, 196, 0.06)',
borderBottom: '1px solid rgba(32, 107, 196, 0.15)'
}}
>
<div className="d-flex align-items-start justify-content-between">
{/* Левая часть: основная информация */}
<div className="d-flex align-items-start flex-grow-1" style={{ minWidth: 0 }}>
{isUnassigned ? (
<>
<span className="avatar avatar-lg bg-secondary-lt me-3 flex-shrink-0">
<IconServer size={24} className="text-muted" />
</span>
<div className="flex-grow-1">
<h3 className="mb-1 fw-bold text-muted">Без привязки к серверу</h3>
<div className="d-flex align-items-start flex-wrap gap-3">
{/* Левая часть: основная информация */}
<div className="d-flex align-items-start flex-grow-1" style={{ minWidth: 0 }}>
{isUnassigned ? (
<>
<span className="avatar avatar-lg bg-secondary-lt me-3 flex-shrink-0">
<IconServer size={24} className="text-muted" />
</span>
<div className="flex-grow-1">
<h3 className="mb-0 fw-bold text-muted">Без привязки к серверу</h3>
</div>
</>
) : (
<>
<span className="avatar avatar-lg bg-primary-lt me-3 flex-shrink-0">
<IconServer size={24} className="text-primary" />
</span>
<div className="flex-grow-1" style={{ minWidth: 0 }}>
{/* Основная строка: название сервера */}
<div className="d-flex align-items-center mb-2 flex-wrap gap-2">
<h3 className="mb-0 fw-bold" style={{ fontSize: '1.25rem', lineHeight: '1.3' }}>
{server?.dns ? server.dns.split('.')[0] : (server?.name || serverId)}
</h3>
{server?.country && (
<span className="badge bg-blue-lt text-blue d-flex align-items-center flex-shrink-0" style={{ fontSize: '0.875rem', padding: '4px 10px' }}>
<span className="me-1" style={{ fontSize: '1rem' }}>{countryToFlag(server.country)}</span>
{server.country}
</span>
)}
{server?.provider && (
<span className={`badge bg-${getProviderColor(server.provider)}-lt text-${getProviderColor(server.provider)} flex-shrink-0`} style={{ fontSize: '0.875rem', padding: '4px 10px' }}>
{server.provider}
</span>
)}
</div>
</>
) : (
<>
<span className="avatar avatar-lg bg-primary-lt me-3 flex-shrink-0">
<IconServer size={24} className="text-primary" />
</span>
<div className="flex-grow-1" style={{ minWidth: 0 }}>
{/* Основная строка: название сервера */}
<div className="d-flex align-items-center mb-2 flex-wrap gap-2">
<h3 className="mb-0 fw-bold" style={{ fontSize: '1.25rem' }}>
{server?.dns ? server.dns.split('.')[0] : (server?.name || serverId)}
</h3>
{server?.country && (
<span className="badge bg-blue-lt text-blue d-flex align-items-center" style={{ fontSize: '0.875rem', padding: '4px 10px' }}>
<span className="me-1" style={{ fontSize: '1rem' }}>{countryToFlag(server.country)}</span>
{server.country}
</span>
)}
{server?.provider && (
<span className={`badge bg-${getProviderColor(server.provider)}-lt text-${getProviderColor(server.provider)}`} style={{ fontSize: '0.875rem', padding: '4px 10px' }}>
{server.provider}
</span>
)}
</div>
{/* Дополнительная информация: DNS и IP */}
<div className="d-flex align-items-center flex-wrap gap-3" style={{ fontSize: '0.875rem' }}>
{server?.dns && (
<div className="d-flex align-items-center">
<span className="text-muted me-1">DNS:</span>
<code className="text-dark" style={{ fontSize: '0.875rem', backgroundColor: 'rgba(0, 0, 0, 0.04)', padding: '2px 6px', borderRadius: '4px' }}>
{server.dns}
</code>
</div>
)}
{server?.ip && (
<div className="d-flex align-items-center">
<span className="text-muted me-1">IP:</span>
<code className="text-dark" style={{ fontSize: '0.875rem', backgroundColor: 'rgba(0, 0, 0, 0.04)', padding: '2px 6px', borderRadius: '4px' }}>
{server.ip}
</code>
</div>
)}
</div>
{/* Дополнительная информация: DNS и IP */}
<div className="d-flex align-items-center flex-wrap gap-3" style={{ fontSize: '0.875rem' }}>
{server?.dns && (
<div className="d-flex align-items-center flex-shrink-0">
<span className="text-muted me-1">DNS:</span>
<code className="text-dark" style={{ fontSize: '0.875rem', backgroundColor: 'rgba(0, 0, 0, 0.04)', padding: '2px 6px', borderRadius: '4px', wordBreak: 'break-all' }}>
{server.dns}
</code>
</div>
)}
{server?.ip && (
<div className="d-flex align-items-center flex-shrink-0">
<span className="text-muted me-1">IP:</span>
<code className="text-dark" style={{ fontSize: '0.875rem', backgroundColor: 'rgba(0, 0, 0, 0.04)', padding: '2px 6px', borderRadius: '4px' }}>
{server.ip}
</code>
</div>
)}
</div>
</>
)}
</div>
{/* Правая часть: счетчик и действия */}
<div className="d-flex align-items-center gap-2 flex-shrink-0 ms-3">
<span className="badge bg-primary text-white" style={{ fontSize: '0.875rem', padding: '6px 12px', fontWeight: '600' }}>
{items.length} {type === 'gateway' ? (items.length === 1 ? 'gateway' : 'gateways') : (items.length === 1 ? 'интерфейс' : 'интерфейсов')}
</span>
{type === 'interface' && !isUnassigned && items.length > 0 && (
<button
className="btn btn-primary btn-sm"
onClick={(e) => {
e.stopPropagation();
handleGenerateMikrotikCodeForServer(serverId, type);
}}
title="Получить код MikroTik для всех интерфейсов этого сервера"
style={{ whiteSpace: 'nowrap' }}
>
<IconCode size={16} className="me-1" />
Код для MikroTik
</button>
)}
</div>
</div>
</>
)}
</div>
{/* Правая часть: счетчик и действия */}
<div className="d-flex align-items-center gap-2 flex-shrink-0">
<span className="badge bg-primary text-white" style={{ fontSize: '0.875rem', padding: '6px 12px', fontWeight: '600', whiteSpace: 'nowrap' }}>
{items.length} {type === 'gateway' ? (items.length === 1 ? 'gateway' : 'gateways') : (items.length === 1 ? 'интерфейс' : 'интерфейсов')}
</span>
{type === 'interface' && !isUnassigned && items.length > 0 && (
<button
className="btn btn-primary btn-sm flex-shrink-0"
onClick={(e) => {
e.stopPropagation();
handleGenerateMikrotikCodeForServer(serverId, type);
}}
title="Получить код MikroTik для всех интерфейсов этого сервера"
style={{ whiteSpace: 'nowrap' }}
>
<IconCode size={16} className="me-1" />
Код для MikroTik
</button>
)}
</div>
</div>
{/* Items - улучшенное расположение */}
<div className="card-body p-4">
{items.length === 0 ? (
<div className="text-center text-muted py-3">
<small>Нет элементов для отображения</small>
</div>
) : (
<div className="row g-3">
{items.map(item => (
<div key={item.id} className="col-12 col-lg-6">
{type === 'gateway' ? renderGatewayCard(item) :
type === 'interface' ? renderInterfaceCard(item, serverId) :
type === 'pool' ? renderPoolCard(item) : null}
</div>
))}
</div>
)}
</div>
</div>
{/* Items - улучшенное расположение */}
<div className="card-body p-4">
{items.length === 0 ? (
<div className="text-center text-muted py-3">
<small>Нет элементов для отображения</small>
</div>
) : (
<div className="row row-cards g-3">
{items.map(item => (
<div key={item.id} className="col-12 col-lg-6">
{type === 'gateway' ? renderGatewayCard(item) :
type === 'interface' ? renderInterfaceCard(item, serverId) :
type === 'pool' ? renderPoolCard(item) : null}
</div>
))}
</div>
)}
</div>
</div>
);
@@ -2410,7 +2403,7 @@ function NetworkConfigManager() {
const IndicatorIcon = IconDatabase;
return (
<div className="card mb-2" style={{ borderRadius: '12px', maxWidth: '100%' }}>
<div className="card" style={{ borderRadius: '12px', maxWidth: '100%' }}>
<div className="card-body p-0">
<div className="d-flex align-items-stretch">
{/* Левая часть: тип индикатора */}
@@ -2536,7 +2529,7 @@ function NetworkConfigManager() {
}
return (
<div className="card mb-2" style={{ borderRadius: '12px', maxWidth: '100%' }}>
<div className="card" style={{ borderRadius: '12px', maxWidth: '100%' }}>
<div className="card-body p-0">
<div className="d-flex align-items-stretch">
{/* Левая часть: тип индикатора (как в ServerCard) */}
@@ -2662,7 +2655,7 @@ function NetworkConfigManager() {
const displayIp = displayRemoteIp !== '—' ? displayRemoteIp : displayLocalIp;
return (
<div className="card mb-2" style={{ borderRadius: '12px', maxWidth: '100%' }}>
<div className="card" style={{ borderRadius: '12px', maxWidth: '100%' }}>
<div className="card-body p-0">
<div className="d-flex align-items-stretch">
{/* Левая часть: тип интерфейса (как в ServerCard) */}