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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m47s
This commit is contained in:
@@ -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) */}
|
||||
|
||||
Reference in New Issue
Block a user