Modified Patch Panel component

This commit is contained in:
2026-05-14 02:25:08 -05:00
parent a815d19e52
commit 1ec5ab4bf5
6 changed files with 83 additions and 3 deletions

View File

@@ -27,6 +27,10 @@ Datacenter Modeler is a React-based web application for designing datacenter rac
- Asset tag
- Power watts
- Notes
- Edit patch panel-specific properties:
- Copper or fiber optic medium
- Number of ports
- Port identification
- Save diagrams as JSON
- Load previously saved JSON diagrams
- Browser autosave with `localStorage`
@@ -128,7 +132,7 @@ Diagram data is stored as JSON with this top-level shape:
}
```
Each rack/cabinet contains its own metadata and an array of installed equipment. Equipment records include type, name, size, U position, manufacturer/model details, asset information, power, notes, and display color.
Each rack/cabinet contains its own metadata and an array of installed equipment. Equipment records include type, name, size, U position, manufacturer/model details, asset information, power, notes, and display color. Patch panel records also include medium, port count, and port identification metadata.
## Project Structure
@@ -156,4 +160,3 @@ Each rack/cabinet contains its own metadata and an array of installed equipment.
- JSON export is the portable save format for moving diagrams between browsers or computers.
- PDF and image exports are generated from the rendered workspace.
- The current implementation is client-side only; no backend server or database is required.

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Datacenter Modeler</title>
</head>
<body>

8
public/favicon.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="12" fill="#0f1720"/>
<rect x="15" y="10" width="34" height="44" rx="4" fill="#1e293b" stroke="#7dd3fc" stroke-width="3"/>
<path d="M21 18h22M21 26h22M21 34h22M21 42h22" stroke="#f59e0b" stroke-width="3" stroke-linecap="round"/>
<circle cx="25" cy="50" r="2" fill="#22c55e"/>
<circle cx="32" cy="50" r="2" fill="#7dd3fc"/>
<circle cx="39" cy="50" r="2" fill="#ef4444"/>
</svg>

After

Width:  |  Height:  |  Size: 482 B

View File

@@ -462,7 +462,11 @@ export default function App() {
[...rack.equipment]
.sort((a, b) => b.uStart - a.uStart)
.forEach((item) => {
const line = `U${item.uStart}-${item.uStart + item.sizeU - 1}: ${item.name} | ${item.manufacturer || 'n/a'} ${item.model || ''} | ${item.assetTag || 'no asset tag'}`;
const patchPanelDetails =
item.type === 'patch-panel'
? ` | ${item.patchPanelMedium === 'fiberoptic' ? 'Fiber optic' : 'Copper'} | ${item.patchPanelPorts} ports`
: '';
const line = `U${item.uStart}-${item.uStart + item.sizeU - 1}: ${item.name} | ${item.manufacturer || 'n/a'} ${item.model || ''} | ${item.assetTag || 'no asset tag'}${patchPanelDetails}`;
pdf.text(line.slice(0, 115), margin + 12, y);
y += 14;
});
@@ -949,6 +953,54 @@ function PropertiesPanel({
onChange={(event) => onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, { assetTag: event.target.value })}
fullWidth
/>
{selectedEquipment.type === 'patch-panel' && (
<>
<Divider />
<Typography variant="subtitle2" sx={{ fontWeight: 800 }}>
Patch Panel
</Typography>
<FormControl fullWidth>
<InputLabel>Medium</InputLabel>
<Select
value={selectedEquipment.patchPanelMedium || 'copper'}
label="Medium"
onChange={(event: SelectChangeEvent) =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
patchPanelMedium: event.target.value as Equipment['patchPanelMedium'],
})
}
>
<MenuItem value="copper">Copper</MenuItem>
<MenuItem value="fiberoptic">Fiber optic</MenuItem>
</Select>
</FormControl>
<TextField
label="Number of ports"
type="number"
value={selectedEquipment.patchPanelPorts}
inputProps={{ min: 0, max: 10000 }}
onChange={(event) =>
onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, {
patchPanelPorts: clampNumber(event.target.value, 0, 10000, selectedEquipment.patchPanelPorts),
})
}
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
/>
</>
)}
<TextField
label="Power watts"
type="number"

View File

@@ -17,6 +17,8 @@ export type ComponentType =
export type LibraryCategory = 'container' | 'equipment';
export type PatchPanelMedium = '' | 'copper' | 'fiberoptic';
export interface LibraryItem {
type: ComponentType;
category: LibraryCategory;
@@ -38,6 +40,9 @@ export interface Equipment {
serialNumber: string;
assetTag: string;
powerWatts: number;
patchPanelMedium: PatchPanelMedium;
patchPanelPorts: number;
patchPanelPortIdentification: string;
notes: string;
color: string;
}

View File

@@ -53,6 +53,9 @@ export const createEquipment = (type: string, rack: RackContainer, preferredU: n
serialNumber: '',
assetTag: '',
powerWatts: 0,
patchPanelMedium: libraryItem.type === 'patch-panel' ? 'copper' : '',
patchPanelPorts: libraryItem.type === 'patch-panel' ? 24 : 0,
patchPanelPortIdentification: '',
notes: '',
color: libraryItem.color,
};
@@ -150,6 +153,14 @@ export const normalizeDiagram = (value: unknown): DiagramData => {
serialNumber: item.serialNumber || '',
assetTag: item.assetTag || '',
powerWatts: clampNumber(item.powerWatts, 0, 100000, 0),
patchPanelMedium:
item.patchPanelMedium === 'fiberoptic' || item.patchPanelMedium === 'copper'
? item.patchPanelMedium
: libraryItem.type === 'patch-panel'
? 'copper'
: '',
patchPanelPorts: clampNumber(item.patchPanelPorts, 0, 10000, libraryItem.type === 'patch-panel' ? 24 : 0),
patchPanelPortIdentification: item.patchPanelPortIdentification || '',
notes: item.notes || '',
color: item.color || libraryItem.color,
};