@@ -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 - 28 V < / 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 порт.