Added fields for port mappings
This commit is contained in:
1 parent
67c82f61c7
commit
dd8e1b2418
5 files changed
+266
-21
No files matched your search
+187
-15
@@ -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
@@ -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
@@ -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 [];
|
||||
|
||||
Reference in new issue
Block a user