+ {/* Standard ports Group */}
+
{row.map((port) => {
const hasVlan = port.vlans.length > 0;
const vlanColor = hasVlan
? port.mode === 'ACCESS'
? port.vlans[0]?.vlan?.color || '#10b981'
- : '#8b5cf6'
+ : '#a78bfa'
: '#475569';
const isCablingSource = cablingFromPortId === port.id;
@@ -195,32 +193,28 @@ export function ModuleBlock({ module }: ModuleBlockProps) {
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => handlePortClick(e, port.id)}
aria-label={`Port ${port.portNumber}`}
- title={`Port ${port.portNumber}${port.label ? ` · ${port.label}` : ''}${
- hasVlan ? ` (VLAN ${port.vlans.map((v) => v.vlan?.vlanId).filter(Boolean).join(',')})` : ''
- }\nShift+Click for settings`}
- style={{ backgroundColor: vlanColor, borderColor: 'rgba(0,0,0,0.2)' }}
+ title={`Port ${port.portNumber}\n${port.portType}${port.label ? ` · ${port.label}` : ''}`}
+ style={{ backgroundColor: vlanColor }}
className={cn(
- 'w-2.5 h-2.5 rounded-sm border transition-all shrink-0 hover:scale-110 hover:brightness-125',
- isCablingSource && 'ring-2 ring-blue-400 ring-offset-1 ring-offset-slate-900 animate-pulse'
+ 'w-2 h-2 rounded-full border border-black/20 hover:scale-125 transition-all outline-none',
+ isCablingSource && 'ring-2 ring-white ring-offset-1 ring-offset-slate-900 animate-pulse'
)}
/>
);
})}
- {/* SFP/WAN group (on first row) */}
+ {/* SFP/WAN Group (push to right) */}
{rowIdx === 0 && sidePorts.length > 0 && (
-
+
{sidePorts.map((port) => {
const hasVlan = port.vlans.length > 0;
- const isSfp = ['SFP', 'SFP_PLUS', 'QSFP'].includes(port.portType);
+ const isSfp = port.portType?.includes('SFP');
const isWan = port.portType === 'WAN';
const vlanColor = hasVlan
- ? port.mode === 'ACCESS'
- ? port.vlans[0]?.vlan?.color || '#10b981'
- : '#8b5cf6'
- : isWan ? '#3b82f6' : '#64748b';
+ ? port.vlans[0]?.vlan?.color || '#3b82f6'
+ : isWan ? '#2563eb' : '#94a3b8';
const isCablingSource = cablingFromPortId === port.id;
@@ -230,16 +224,12 @@ export function ModuleBlock({ module }: ModuleBlockProps) {
data-port-id={port.id}
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => handlePortClick(e, port.id)}
- aria-label={`${port.portType} Port ${port.portNumber}`}
- title={`${port.portType} Port ${port.portNumber}${port.label ? ` · ${port.label}` : ''}${
- hasVlan ? ` (VLAN ${port.vlans.map((v) => v.vlan?.vlanId).filter(Boolean).join(',')})` : ''
- }\nShift+Click for settings`}
- style={{ backgroundColor: vlanColor, borderColor: isSfp ? 'rgba(255,255,255,0.3)' : 'rgba(0,0,0,0.2)' }}
+ title={`${port.portType} ${port.portNumber}`}
+ style={{ backgroundColor: vlanColor }}
className={cn(
- 'w-2.5 h-2.5 border transition-all shrink-0 hover:scale-110 hover:brightness-125',
- isSfp ? 'rounded-none rotate-45 scale-[0.85]' : 'rounded-full',
- isCablingSource && 'ring-2 ring-blue-400 ring-offset-1 ring-offset-slate-900 animate-pulse',
- isWan && 'ring-1 ring-blue-400/50'
+ 'w-2.5 h-2.5 transition-transform hover:scale-125 border border-black/40',
+ isSfp ? 'rounded-none rotate-45 scale-75' : 'rounded-full ring-1 ring-white/10',
+ isCablingSource && 'ring-2 ring-white ring-offset-1 ring-offset-slate-900 animate-pulse'
)}
/>
);
@@ -249,7 +239,7 @@ export function ModuleBlock({ module }: ModuleBlockProps) {
))}
{hiddenPortCount > 0 && (
-
+{hiddenPortCount} more
+
+{hiddenPortCount} ports
)}
) : (
diff --git a/scripts/check_ports.ts b/scripts/check_ports.ts
new file mode 100644
index 0000000..7f33283
--- /dev/null
+++ b/scripts/check_ports.ts
@@ -0,0 +1,12 @@
+
+import { PrismaClient } from '@prisma/client';
+const prisma = new PrismaClient();
+
+async function check() {
+ const modules = await prisma.module.findMany({
+ include: { ports: true }
+ });
+ console.log(JSON.stringify(modules, null, 2));
+}
+
+check().catch(console.error);