add hAP ac2
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
sudo apt install docker-compose-v2 git -y
|
||||
git clone https://git.core.uz/mikrotik/ROSzetta.git
|
||||
sudo docker compose up -d --build
|
||||
@@ -14,6 +14,11 @@ export interface DeviceMockupProps {
|
||||
const isHapAcLite = (b?: string | null): boolean =>
|
||||
!!b && /h\s*A\s*P\s*ac\s*lite/i.test(b);
|
||||
|
||||
// hAP ac² (RBD52G-5HacD2HnD): отличаем по цифре «2» / «²» после «ac»,
|
||||
// чтобы случайно не перехватить hAP ac lite.
|
||||
const isHapAc2 = (b?: string | null): boolean =>
|
||||
!!b && (/h\s*A\s*P\s*ac\s*[²2]/i.test(b) || /RBD52G/i.test(b));
|
||||
|
||||
const isHapLike = (b?: string | null): boolean => !!b && /\bh\s*A\s*P\b/i.test(b);
|
||||
|
||||
const isRb5009 = (b?: string | null): boolean =>
|
||||
@@ -49,7 +54,13 @@ function portColor(it: InterfaceInfo | undefined): { fill: string; stroke: strin
|
||||
}
|
||||
|
||||
export default function DeviceMockup({ boardName, interfaces }: DeviceMockupProps) {
|
||||
if (isHapAcLite(boardName) || (isHapLike(boardName) && interfaces.filter((it) => /^ether/.test(it.name)).length === 5)) {
|
||||
if (isHapAcLite(boardName)) {
|
||||
return <HapAcLiteMockup interfaces={interfaces} />;
|
||||
}
|
||||
if (isHapAc2(boardName)) {
|
||||
return <HapAc2Mockup interfaces={interfaces} />;
|
||||
}
|
||||
if (isHapLike(boardName) && interfaces.filter((it) => /^ether/.test(it.name)).length === 5) {
|
||||
return <HapAcLiteMockup interfaces={interfaces} />;
|
||||
}
|
||||
if (isRb5009(boardName)) {
|
||||
@@ -228,6 +239,141 @@ function HapAcLiteMockup({ interfaces }: { interfaces: InterfaceInfo[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
// --------- hAP ac² ---------
|
||||
// Чёрный пластиковый корпус (RBD52G-5HacD2HnD).
|
||||
// Слева: DC 12-28V, утопленная кнопка res/wps, индикаторы pwr / usr.
|
||||
// Справа: 5 GigE портов — ether1 «Internet/PoE in», ether2..ether5 «LAN».
|
||||
// PoE-out нет (в отличие от hAP ac lite).
|
||||
|
||||
function HapAc2Mockup({ interfaces }: { interfaces: InterfaceInfo[] }) {
|
||||
const ports = [
|
||||
{ name: 'ether1', label: '1', accent: 'poe-in' as const },
|
||||
{ name: 'ether2', label: '2', accent: null as const },
|
||||
{ name: 'ether3', label: '3', accent: null as const },
|
||||
{ name: 'ether4', label: '4', accent: null as const },
|
||||
{ name: 'ether5', label: '5', accent: null as const },
|
||||
];
|
||||
|
||||
// Соотношение фото задней панели ~4.3:1. При height: 62px ширина ≈ 268px.
|
||||
const W = 1180, H = 274;
|
||||
const bodyR = 20;
|
||||
const portW = 130, portH = 130;
|
||||
const portGap = 14;
|
||||
const firstPortX = 410;
|
||||
const portsTopY = 60;
|
||||
const lanStartX = firstPortX + portW + portGap;
|
||||
const lanSpanW = 4 * portW + 3 * portGap;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="text-xs text-mk-mute mb-2">
|
||||
Лицевая панель <b>hAP ac²</b> · подсветка портов в реальном времени
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<svg
|
||||
viewBox={`0 0 ${W} ${H}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ height: '62px', width: 'auto', maxWidth: '100%', display: 'block' }}
|
||||
>
|
||||
{/* Чёрный пластиковый корпус */}
|
||||
<rect x="2" y="2" width={W - 4} height={H - 4} rx={bodyR} ry={bodyR} fill="#1f1f1f" stroke="#050505" strokeWidth="2" />
|
||||
{/* Утопленная плашка отсека (чуть темнее, со внутренней тенью обводки) */}
|
||||
<rect x="20" y="22" width={W - 40} height={H - 64} rx="12" fill="#161616" stroke="#000" strokeWidth="1" />
|
||||
|
||||
{/* DC разъём */}
|
||||
<circle cx="92" cy="148" r="36" fill="#0a0a0a" stroke="#3a3a3a" strokeWidth="3" />
|
||||
<circle cx="92" cy="148" r="10" fill="#1a1a1a" stroke="#000" strokeWidth="2" />
|
||||
<text x="92" y="235" fontSize="22" fill="#ffffff" textAnchor="middle" fontWeight="700">DC</text>
|
||||
<text x="92" y="256" fontSize="14" fill="#cccccc" textAnchor="middle">12-28V</text>
|
||||
|
||||
{/* res/wps — утопленная кнопка */}
|
||||
<circle cx="188" cy="148" r="10" fill="#0a0a0a" stroke="#555" strokeWidth="1.5" />
|
||||
<circle cx="188" cy="148" r="3" fill="#222" />
|
||||
<text x="188" y="92" fontSize="14" fill="#ffffff" textAnchor="middle" fontWeight="600">res/wps</text>
|
||||
|
||||
{/* pwr LED */}
|
||||
<circle cx="252" cy="148" r="5" fill="#1f6f1f" />
|
||||
<text x="252" y="92" fontSize="14" fill="#ffffff" textAnchor="middle" fontWeight="600">pwr</text>
|
||||
|
||||
{/* usr LED */}
|
||||
<circle cx="312" cy="148" r="5" fill="#3a3a3a" />
|
||||
<text x="312" y="92" fontSize="14" fill="#ffffff" textAnchor="middle" fontWeight="600">usr</text>
|
||||
|
||||
{/* Цифры над портами */}
|
||||
{ports.map((p, i) => {
|
||||
const x = firstPortX + i * (portW + portGap);
|
||||
return (
|
||||
<text
|
||||
key={`lbl-${p.name}`}
|
||||
x={x + portW / 2}
|
||||
y="48"
|
||||
fontSize="22"
|
||||
fill="#ffffff"
|
||||
fontWeight="700"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{p.label}
|
||||
</text>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Порты */}
|
||||
{ports.map((p, i) => {
|
||||
const x = firstPortX + i * (portW + portGap);
|
||||
const it = findPort(interfaces, p.name);
|
||||
const col = portColor(it);
|
||||
return (
|
||||
<g key={p.name}>
|
||||
{/* Металлический ободок RJ45 */}
|
||||
<rect x={x} y={portsTopY} width={portW} height={portH} rx="6" fill="#c8c8c8" stroke="#666" strokeWidth="1.5" />
|
||||
{/* Внутренний экран — закрашивается под статус */}
|
||||
<rect x={x + 8} y={portsTopY + 8} width={portW - 16} height={portH - 16} rx="3" fill={col.fill} stroke={col.stroke} strokeWidth="3" />
|
||||
{/* RJ45 «зубчики» */}
|
||||
<rect x={x + 24} y={portsTopY + 16} width={portW - 48} height="18" fill="#000" />
|
||||
<rect x={x + 32} y={portsTopY + 34} width={portW - 64} height="10" fill="#000" />
|
||||
{/* LED-индикатор линка */}
|
||||
<circle
|
||||
cx={x + portW - 18}
|
||||
cy={portsTopY + portH - 18}
|
||||
r="5"
|
||||
fill={it?.running ? '#22c55e' : it?.disabled ? '#777' : '#5a1a1a'}
|
||||
/>
|
||||
{/* Имя интерфейса под портом — мелким шрифтом, чтобы не сливалось с подписями групп */}
|
||||
<text x={x + portW / 2} y={portsTopY + portH + 16} fontSize="11" fill="#888" textAnchor="middle">
|
||||
{p.name}
|
||||
</text>
|
||||
<title>
|
||||
{p.name} (порт {p.label}){p.accent === 'poe-in' ? ' · Internet / PoE in' : ' · LAN'}
|
||||
{'\n'}статус: {col.label}
|
||||
{it?.comment ? `\ncomment: ${it.comment}` : ''}
|
||||
{it?.mac_address ? `\nmac: ${it.mac_address}` : ''}
|
||||
</title>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Группа Internet/PoE in под портом 1 */}
|
||||
<line x1={firstPortX - 2} y1={H - 36} x2={firstPortX + portW + 2} y2={H - 36} stroke="#9aa0a6" strokeWidth="1.2" />
|
||||
<circle cx={firstPortX - 2} cy={H - 36} r="3" fill="#9aa0a6" />
|
||||
<circle cx={firstPortX + portW + 2} cy={H - 36} r="3" fill="#9aa0a6" />
|
||||
<text x={firstPortX + portW / 2} y={H - 14} fontSize="14" fill="#ffffff" textAnchor="middle" fontWeight="600">
|
||||
Internet/PoE in
|
||||
</text>
|
||||
|
||||
{/* Группа LAN под портами 2-5 */}
|
||||
<line x1={lanStartX - 2} y1={H - 36} x2={lanStartX + lanSpanW + 2} y2={H - 36} stroke="#9aa0a6" strokeWidth="1.2" />
|
||||
<circle cx={lanStartX - 2} cy={H - 36} r="3" fill="#9aa0a6" />
|
||||
<circle cx={lanStartX + lanSpanW + 2} cy={H - 36} r="3" fill="#9aa0a6" />
|
||||
<text x={lanStartX + lanSpanW / 2} y={H - 14} fontSize="14" fill="#ffffff" textAnchor="middle" fontWeight="600">
|
||||
LAN
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
<MockupLegend />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --------- RB5009UG+S+ ---------
|
||||
// Чёрный корпус, 8 GigE портов (ether1..ether8) + 1 SFP+ (sfp-sfpplus1).
|
||||
// Слева: DC jack 12-57V, кнопка R (reset), USB 3.0 порт.
|
||||
|
||||
Reference in New Issue
Block a user