feat(server): expand server types to include BGP and DNS; enhance validation for required fields and tunnel requirements in server configuration
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m25s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m25s
This commit is contained in:
+14
-4
@@ -301,10 +301,14 @@ app.post('/api/ip-ranges', writeLimiter, ipRangesRoutes.post);
|
|||||||
// === JSON DATA ROUTES (используют фабрики) ===
|
// === JSON DATA ROUTES (используют фабрики) ===
|
||||||
|
|
||||||
// Servers
|
// Servers
|
||||||
const SERVER_TYPES = ['jumphost', 'exit'];
|
const SERVER_TYPES = ['jumphost', 'exit', 'bgp', 'dns'];
|
||||||
|
const TYPES_NEED_TUNNEL = ['jumphost', 'exit']; // Типы, которым нужен туннель
|
||||||
|
const TYPES_NEED_GATEWAYS = ['jumphost', 'exit']; // Типы, которым нужны gateways
|
||||||
|
|
||||||
const serversRoutes = createJsonDataRoutes('servers.json', (server, i) => {
|
const serversRoutes = createJsonDataRoutes('servers.json', (server, i) => {
|
||||||
if (!server.ip || !server.dns || !server.country || !server.provider || !server.tunnel) {
|
// Базовые обязательные поля
|
||||||
return `Server at index ${i} is missing required fields`;
|
if (!server.ip || !server.dns || !server.country || !server.provider) {
|
||||||
|
return `Server at index ${i} is missing required fields (ip, dns, country, provider)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedType = String(server.type || '').toLowerCase();
|
const normalizedType = String(server.type || '').toLowerCase();
|
||||||
@@ -312,7 +316,13 @@ const serversRoutes = createJsonDataRoutes('servers.json', (server, i) => {
|
|||||||
return `Server at index ${i} has invalid type (allowed: ${SERVER_TYPES.join(', ')})`;
|
return `Server at index ${i} has invalid type (allowed: ${SERVER_TYPES.join(', ')})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedType === 'jumphost' || normalizedType === 'exit') {
|
// Туннель обязателен только для jumphost и exit
|
||||||
|
if (TYPES_NEED_TUNNEL.includes(normalizedType) && !server.tunnel) {
|
||||||
|
return `Server at index ${i} (${normalizedType}) requires tunnel type`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gateways обязательны только для jumphost и exit
|
||||||
|
if (TYPES_NEED_GATEWAYS.includes(normalizedType)) {
|
||||||
if (!Array.isArray(server.gateways) || server.gateways.length === 0) {
|
if (!Array.isArray(server.gateways) || server.gateways.length === 0) {
|
||||||
return `Server at index ${i} must have gateways for type ${normalizedType}`;
|
return `Server at index ${i} must have gateways for type ${normalizedType}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ import {
|
|||||||
IconNetwork,
|
IconNetwork,
|
||||||
IconCloud,
|
IconCloud,
|
||||||
IconRouter,
|
IconRouter,
|
||||||
IconArrowsRightLeft
|
IconArrowsRightLeft,
|
||||||
|
IconRoute,
|
||||||
|
IconWorldWww
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { needsTunnel } from '../../utils/serverUtils.js';
|
||||||
|
|
||||||
// Преобразование кода страны в emoji-флаг
|
// Преобразование кода страны в emoji-флаг
|
||||||
function countryToFlag(isoCode) {
|
function countryToFlag(isoCode) {
|
||||||
@@ -42,10 +45,20 @@ export default function ServerCard({
|
|||||||
}) {
|
}) {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
const isExit = server.type === 'exit';
|
const serverType = (server.type || 'jumphost').toLowerCase();
|
||||||
const primaryGateway = server.gateways?.find(g => g.primary)?.name || server.gateway || '';
|
const primaryGateway = server.gateways?.find(g => g.primary)?.name || server.gateway || '';
|
||||||
const connectionsCount = server.gateways?.length || 0;
|
const connectionsCount = server.gateways?.length || 0;
|
||||||
const serverName = server.dns?.split('.')[0] || server.ip;
|
const serverName = server.dns?.split('.')[0] || server.ip;
|
||||||
|
const showTunnel = needsTunnel(serverType);
|
||||||
|
|
||||||
|
// Конфигурация отображения типов серверов
|
||||||
|
const typeConfig = {
|
||||||
|
jumphost: { label: 'JUMP', color: 'blue', icon: IconRouter },
|
||||||
|
exit: { label: 'EXIT', color: 'red', icon: IconArrowsRightLeft },
|
||||||
|
bgp: { label: 'BGP', color: 'purple', icon: IconRoute },
|
||||||
|
dns: { label: 'DNS', color: 'cyan', icon: IconWorldWww },
|
||||||
|
};
|
||||||
|
const currentTypeConfig = typeConfig[serverType] || typeConfig.jumphost;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -62,7 +75,7 @@ export default function ServerCard({
|
|||||||
<div className="d-flex align-items-stretch">
|
<div className="d-flex align-items-stretch">
|
||||||
{/* Левая часть: тип сервера */}
|
{/* Левая часть: тип сервера */}
|
||||||
<div
|
<div
|
||||||
className={`d-flex align-items-center justify-content-center px-3 ${isExit ? 'bg-red-lt' : 'bg-blue-lt'}`}
|
className={`d-flex align-items-center justify-content-center px-3 bg-${currentTypeConfig.color}-lt`}
|
||||||
style={{
|
style={{
|
||||||
minWidth: '80px',
|
minWidth: '80px',
|
||||||
borderTopLeftRadius: selected ? '10px' : '11px',
|
borderTopLeftRadius: selected ? '10px' : '11px',
|
||||||
@@ -74,15 +87,13 @@ export default function ServerCard({
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
{selected ? (
|
{selected ? (
|
||||||
<IconCircleCheck size={20} className={isExit ? 'text-red' : 'text-blue'} />
|
<IconCircleCheck size={20} className={`text-${currentTypeConfig.color}`} />
|
||||||
) : isExit ? (
|
|
||||||
<IconArrowsRightLeft size={20} className="text-red" />
|
|
||||||
) : (
|
) : (
|
||||||
<IconRouter size={20} className="text-blue" />
|
<currentTypeConfig.icon size={20} className={`text-${currentTypeConfig.color}`} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={`fw-bold ${isExit ? 'text-red' : 'text-blue'}`} style={{ fontSize: '0.65rem', letterSpacing: '0.5px' }}>
|
<div className={`fw-bold text-${currentTypeConfig.color}`} style={{ fontSize: '0.65rem', letterSpacing: '0.5px' }}>
|
||||||
{isExit ? 'EXIT' : 'JUMP'}
|
{currentTypeConfig.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,10 +125,12 @@ export default function ServerCard({
|
|||||||
<code className="text-muted" style={{ fontSize: '0.8rem' }}>
|
<code className="text-muted" style={{ fontSize: '0.8rem' }}>
|
||||||
{server.dns || server.ip}
|
{server.dns || server.ip}
|
||||||
</code>
|
</code>
|
||||||
<span className="badge bg-secondary-lt text-secondary">
|
{showTunnel && server.tunnel && (
|
||||||
<IconNetwork size={12} className="me-1" />
|
<span className="badge bg-secondary-lt text-secondary">
|
||||||
{server.tunnel}
|
<IconNetwork size={12} className="me-1" />
|
||||||
</span>
|
{server.tunnel}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{connectionsCount > 0 && (
|
{connectionsCount > 0 && (
|
||||||
<span className="badge bg-green-lt text-green">
|
<span className="badge bg-green-lt text-green">
|
||||||
<IconCloud size={12} className="me-1" />
|
<IconCloud size={12} className="me-1" />
|
||||||
|
|||||||
Reference in New Issue
Block a user