Added fields for port mappings

This commit is contained in:
pucketm2016 committed 2026-05-14 18:17:17 -05:00
1 parent 67c82f61c7
commit dd8e1b2418
5 files changed
+266 -21

No files matched your search

+187 -15
View File
@@ -74,6 +74,16 @@ import {
const STORAGE_KEY = 'datacenter-modeler.diagram';
const RACK_ROW_HEIGHT = 14;
const PORT_MAPPED_COMPONENTS = new Set<ComponentType>([
'blade-chassis',
'firewall',
'kvm',
'router',
'server-rack',
'storage-array',
'storage-switch',
'switch',
]);
const icons: Record<ComponentType, JSX.Element> = {
rack: <Dns fontSize="small" />,
@@ -523,8 +533,9 @@ export default function App() {
.forEach((item) => {
const patchPanelDetails =
item.type === 'patch-panel'
? ` | ${item.patchPanelMedium === 'fiberoptic' ? 'Fiber optic' : 'Copper'} | ${item.patchPanelPorts} ports`
? ` | ${item.patchPanelMedium === 'fiberoptic' ? 'Fiber optic' : 'Copper'} | ${item.patchPanelPorts} ports | ${item.patchPanelPortMap.length} mapped`
: '';
const networkPortDetails = PORT_MAPPED_COMPONENTS.has(item.type) ? ` | ${item.networkPorts.length} mapped ports` : '';
const pduDetails =
item.type === 'pdu'
? ` | ${item.pduMount === 'vertical' ? `Vertical ${item.pduSide}, ${item.sizeU}U wide` : `Horizontal ${item.sizeU}U`} | In ${item.pduInputVoltage || 'n/a'} | ${item.pduOutputPorts.length} outputs${item.pduManaged ? ` | Managed ${item.pduHostname || 'no host'}` : ''}`
@@ -533,7 +544,7 @@ export default function App() {
item.type === 'pdu' && item.pduMount === 'vertical'
? `${item.pduSide === 'left' ? 'Left' : 'Right'} side`
: `U${item.uStart}-${item.uStart + item.sizeU - 1}`;
const line = `${rackPosition}: ${item.name} | ${item.manufacturer || 'n/a'} ${item.model || ''} | ${item.assetTag || 'no asset tag'}${patchPanelDetails}${pduDetails}`;
const line = `${rackPosition}: ${item.name} | ${item.manufacturer || 'n/a'} ${item.model || ''} | ${item.assetTag || 'no asset tag'}${patchPanelDetails}${networkPortDetails}${pduDetails}`;
pdf.text(line.slice(0, 115), margin + 12, y);
y += 14;
});
@@ -1256,19 +1267,180 @@ function PropertiesPanel({
}
fullWidth
/>
<TextField
label="Port identification"
value={selectedEquipment.patchPanelPortIdentification}
onChange={(event) =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
patchPanelPortIdentification: event.target.value,
})
}
multiline
minRows={3}
placeholder="A01-A24, VLAN labels, fiber strands, or room/circuit references"
fullWidth
/>
<Stack spacing={1}>
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
<Typography variant="body2" sx={{ fontWeight: 800 }}>
Port Map
</Typography>
<Button
size="small"
variant="outlined"
startIcon={<Add />}
onClick={() =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
patchPanelPortMap: [
...selectedEquipment.patchPanelPortMap,
{
id: createId('patch-port'),
portId: `Port ${selectedEquipment.patchPanelPortMap.length + 1}`,
cableId: '',
},
],
})
}
>
Add port
</Button>
</Stack>
{selectedEquipment.patchPanelPortMap.length === 0 ? (
<Typography variant="caption" color="text.secondary">
No ports mapped.
</Typography>
) : (
selectedEquipment.patchPanelPortMap.map((port) => (
<Stack key={port.id} direction="row" spacing={1} alignItems="center">
<TextField
label="Port ID"
value={port.portId}
size="small"
onChange={(event) =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
patchPanelPortMap: selectedEquipment.patchPanelPortMap.map((item) =>
item.id === port.id ? { ...item, portId: event.target.value } : item,
),
})
}
sx={{ flex: 1 }}
/>
<TextField
label="Cable ID"
value={port.cableId}
size="small"
onChange={(event) =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
patchPanelPortMap: selectedEquipment.patchPanelPortMap.map((item) =>
item.id === port.id ? { ...item, cableId: event.target.value } : item,
),
})
}
sx={{ flex: 1 }}
/>
<Tooltip title="Delete port">
<IconButton
color="error"
size="small"
onClick={() =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
patchPanelPortMap: selectedEquipment.patchPanelPortMap.filter((item) => item.id !== port.id),
})
}
>
<Delete fontSize="small" />
</IconButton>
</Tooltip>
</Stack>
))
)}
</Stack>
</>
)}
{PORT_MAPPED_COMPONENTS.has(selectedEquipment.type) && (
<>
<Divider />
<Typography variant="subtitle2" sx={{ fontWeight: 800 }}>
Component Ports
</Typography>
<Stack spacing={1}>
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
<Typography variant="body2" sx={{ fontWeight: 800 }}>
Port Map
</Typography>
<Button
size="small"
variant="outlined"
startIcon={<Add />}
onClick={() =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
networkPorts: [
...selectedEquipment.networkPorts,
{
id: createId('network-port'),
portId: `Port ${selectedEquipment.networkPorts.length + 1}`,
cableId: '',
vlan: 1,
},
],
})
}
>
Add port
</Button>
</Stack>
{selectedEquipment.networkPorts.length === 0 ? (
<Typography variant="caption" color="text.secondary">
No component ports mapped.
</Typography>
) : (
selectedEquipment.networkPorts.map((port) => (
<Stack key={port.id} direction="row" spacing={1} alignItems="center">
<TextField
label="Port ID"
value={port.portId}
size="small"
onChange={(event) =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
networkPorts: selectedEquipment.networkPorts.map((item) =>
item.id === port.id ? { ...item, portId: event.target.value } : item,
),
})
}
sx={{ flex: 1 }}
/>
<TextField
label="Cable ID"
value={port.cableId}
size="small"
onChange={(event) =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
networkPorts: selectedEquipment.networkPorts.map((item) =>
item.id === port.id ? { ...item, cableId: event.target.value } : item,
),
})
}
sx={{ flex: 1 }}
/>
<TextField
label="VLAN"
type="number"
value={port.vlan}
size="small"
inputProps={{ min: 1, max: 4094 }}
onChange={(event) =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
networkPorts: selectedEquipment.networkPorts.map((item) =>
item.id === port.id ? { ...item, vlan: clampNumber(event.target.value, 1, 4094, port.vlan) } : item,
),
})
}
sx={{ flex: 0.8 }}
/>
<Tooltip title="Delete port">
<IconButton
color="error"
size="small"
onClick={() =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
networkPorts: selectedEquipment.networkPorts.filter((item) => item.id !== port.id),
})
}
>
<Delete fontSize="small" />
</IconButton>
</Tooltip>
</Stack>
))
)}
</Stack>
</>
)}
{selectedEquipment.type === 'pdu' && (
+15 -1
View File
@@ -20,6 +20,19 @@ export type LibraryCategory = 'container' | 'equipment';
export type PatchPanelMedium = '' | 'copper' | 'fiberoptic';
export interface PatchPanelPort {
id: string;
portId: string;
cableId: string;
}
export interface NetworkPort {
id: string;
portId: string;
cableId: string;
vlan: number;
}
export type PduMount = 'horizontal' | 'vertical';
export type PduSide = 'left' | 'right';
@@ -54,7 +67,8 @@ export interface Equipment {
powerWatts: number;
patchPanelMedium: PatchPanelMedium;
patchPanelPorts: number;
patchPanelPortIdentification: string;
patchPanelPortMap: PatchPanelPort[];
networkPorts: NetworkPort[];
pduMount: PduMount;
pduSide: PduSide;
pduInputVoltage: string;
+52 -3
View File
@@ -1,4 +1,4 @@
import type { DiagramData, Equipment, PduOutputPort, RackContainer } from '../types';
import type { DiagramData, Equipment, NetworkPort, PatchPanelPort, PduOutputPort, RackContainer } from '../types';
import { getLibraryItem } from '../data/componentLibrary';
export const MAX_CONTAINERS = 10;
@@ -55,7 +55,8 @@ export const createEquipment = (type: string, rack: RackContainer, preferredU: n
powerWatts: 0,
patchPanelMedium: libraryItem.type === 'patch-panel' ? 'copper' : '',
patchPanelPorts: libraryItem.type === 'patch-panel' ? 24 : 0,
patchPanelPortIdentification: '',
patchPanelPortMap: [],
networkPorts: [],
pduMount: 'horizontal',
pduSide: 'right',
pduInputVoltage: '',
@@ -155,6 +156,12 @@ export const normalizeDiagram = (value: unknown): DiagramData => {
notes: rack.notes || '',
equipment: Array.isArray(rack.equipment)
? rack.equipment.map((item, itemIndex) => {
const legacyItem = item as Equipment & {
networkPorts?: unknown;
patchPanelPortIdentification?: unknown;
patchPanelPortMap?: unknown;
switchPorts?: unknown;
};
const libraryItem = getLibraryItem(item.type) || getLibraryItem('server-rack')!;
const pduMount = item.pduMount === 'vertical' ? 'vertical' : 'horizontal';
const maxSizeU = libraryItem.type === 'pdu' ? (pduMount === 'vertical' ? 3 : 12) : 24;
@@ -177,7 +184,8 @@ export const normalizeDiagram = (value: unknown): DiagramData => {
? 'copper'
: '',
patchPanelPorts: clampNumber(item.patchPanelPorts, 0, 10000, libraryItem.type === 'patch-panel' ? 24 : 0),
patchPanelPortIdentification: item.patchPanelPortIdentification || '',
patchPanelPortMap: normalizePatchPanelPorts(legacyItem.patchPanelPortMap, legacyItem.patchPanelPortIdentification),
networkPorts: normalizeNetworkPorts(legacyItem.networkPorts ?? legacyItem.switchPorts),
pduMount,
pduSide: item.pduSide === 'left' ? 'left' : 'right',
pduInputVoltage: item.pduInputVoltage || '',
@@ -195,6 +203,47 @@ export const normalizeDiagram = (value: unknown): DiagramData => {
};
};
const normalizeNetworkPorts = (ports: unknown): NetworkPort[] => {
if (!Array.isArray(ports)) {
return [];
}
return ports.map((port, index) => {
const candidate = port as Partial<NetworkPort>;
return {
id: candidate.id || createId('network-port'),
portId: candidate.portId || `Port ${index + 1}`,
cableId: candidate.cableId || '',
vlan: clampNumber(candidate.vlan, 1, 4094, 1),
};
});
};
const normalizePatchPanelPorts = (ports: unknown, legacyIdentification: unknown): PatchPanelPort[] => {
if (Array.isArray(ports)) {
return ports.map((port, index) => {
const candidate = port as Partial<PatchPanelPort>;
return {
id: candidate.id || createId('patch-port'),
portId: candidate.portId || `Port ${index + 1}`,
cableId: candidate.cableId || '',
};
});
}
if (typeof legacyIdentification === 'string' && legacyIdentification.trim()) {
return [
{
id: createId('patch-port'),
portId: legacyIdentification,
cableId: '',
},
];
}
return [];
};
const normalizePduOutputPorts = (ports: unknown): PduOutputPort[] => {
if (!Array.isArray(ports)) {
return [];