refactor(NetworkConfigManager): enhance UI structure and styling for server cards, improving readability and user interaction
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m42s

This commit is contained in:
2026-01-25 14:00:54 +07:00
parent cc77a21ba1
commit 3b858441a2
+107 -77
View File
@@ -2242,92 +2242,122 @@ function NetworkConfigManager() {
const isUnassigned = serverId === '__unassigned__'; const isUnassigned = serverId === '__unassigned__';
return ( return (
<div key={serverId} className="card mb-3"> <div key={serverId} className="mb-4">
{/* Server header - более заметный и читаемый */} {/* Server header - улучшенная структура с четкой иерархией */}
<div <div
className="card-header py-3" className="card mb-3"
style={{ style={{
backgroundColor: 'rgba(32, 107, 196, 0.08)', borderLeft: '4px solid rgba(32, 107, 196, 0.3)',
borderBottom: '2px solid rgba(32, 107, 196, 0.2)' boxShadow: '0 1px 3px rgba(0, 0, 0, 0.08)'
}} }}
> >
<div className="d-flex align-items-center justify-content-between"> <div
<div className="d-flex align-items-center flex-grow-1"> className="card-header py-3 px-4"
{isUnassigned ? ( style={{
<> backgroundColor: 'rgba(32, 107, 196, 0.06)',
<span className="avatar avatar-md bg-secondary-lt me-3"> borderBottom: '1px solid rgba(32, 107, 196, 0.15)'
<IconServer size={20} className="text-muted" /> }}
</span> >
<div> <div className="d-flex align-items-start justify-content-between">
<h4 className="mb-0 fw-semibold text-muted">Без привязки к серверу</h4> {/* Левая часть: основная информация */}
</div> <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">
<span className="avatar avatar-md bg-primary-lt me-3"> <IconServer size={24} className="text-muted" />
<IconServer size={20} className="text-primary" /> </span>
</span> <div className="flex-grow-1">
<div className="flex-grow-1"> <h3 className="mb-1 fw-bold text-muted">Без привязки к серверу</h3>
<div className="d-flex align-items-center flex-wrap gap-2">
<h4 className="mb-0 fw-semibold">
{server?.dns ? server.dns.split('.')[0] : (server?.name || serverId)}
</h4>
{server?.dns && (
<code className="text-muted small" style={{ fontSize: '0.875rem' }}>
{server.dns}
</code>
)}
{server?.ip && (
<code className="text-muted small" style={{ fontSize: '0.875rem' }}>
{server.ip}
</code>
)}
{server?.country && (
<span className="badge bg-blue-lt text-blue" style={{ fontSize: '1rem' }}>
{countryToFlag(server.country)} {server.country}
</span>
)}
{server?.provider && (
<span className={`badge bg-${getProviderColor(server.provider)}-lt text-${getProviderColor(server.provider)}`}>
{server.provider}
</span>
)}
</div> </div>
</div> </>
</> ) : (
)} <>
</div> <span className="avatar avatar-lg bg-primary-lt me-3 flex-shrink-0">
<div className="d-flex align-items-center gap-2"> <IconServer size={24} className="text-primary" />
<span className="badge bg-primary-lt text-primary" style={{ fontSize: '0.875rem', padding: '6px 12px' }}> </span>
{items.length} {type === 'gateway' ? (items.length === 1 ? 'gateway' : 'gateways') : (items.length === 1 ? 'интерфейс' : 'интерфейсов')} <div className="flex-grow-1" style={{ minWidth: 0 }}>
</span> {/* Основная строка: название сервера */}
{type === 'interface' && !isUnassigned && items.length > 0 && ( <div className="d-flex align-items-center mb-2 flex-wrap gap-2">
<button <h3 className="mb-0 fw-bold" style={{ fontSize: '1.25rem' }}>
className="btn btn-outline-primary btn-sm" {server?.dns ? server.dns.split('.')[0] : (server?.name || serverId)}
onClick={(e) => { </h3>
e.stopPropagation(); {server?.country && (
handleGenerateMikrotikCodeForServer(serverId, type); <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>
title="Получить код MikroTik для всех интерфейсов этого сервера" {server.country}
> </span>
<IconCode size={16} className="me-1" /> )}
Код для MikroTik {server?.provider && (
</button> <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>
</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> </div>
</div>
{/* Items */} {/* Items - улучшенное расположение */}
<div className="card-body p-3"> <div className="card-body p-4">
<div className="row row-cards"> {items.length === 0 ? (
{items.map(item => ( <div className="text-center text-muted py-3">
<div key={item.id} className="col-12 col-md-6 col-lg-6"> <small>Нет элементов для отображения</small>
{type === 'gateway' ? renderGatewayCard(item) :
type === 'interface' ? renderInterfaceCard(item, serverId) :
type === 'pool' ? renderPoolCard(item) : null}
</div> </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>
</div> </div>
</div> </div>