Modified Patch Panel component
This commit is contained in:
@@ -27,6 +27,10 @@ Datacenter Modeler is a React-based web application for designing datacenter rac
|
|||||||
- Asset tag
|
- Asset tag
|
||||||
- Power watts
|
- Power watts
|
||||||
- Notes
|
- Notes
|
||||||
|
- Edit patch panel-specific properties:
|
||||||
|
- Copper or fiber optic medium
|
||||||
|
- Number of ports
|
||||||
|
- Port identification
|
||||||
- Save diagrams as JSON
|
- Save diagrams as JSON
|
||||||
- Load previously saved JSON diagrams
|
- Load previously saved JSON diagrams
|
||||||
- Browser autosave with `localStorage`
|
- 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
|
## 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.
|
- JSON export is the portable save format for moving diagrams between browsers or computers.
|
||||||
- PDF and image exports are generated from the rendered workspace.
|
- PDF and image exports are generated from the rendered workspace.
|
||||||
- The current implementation is client-side only; no backend server or database is required.
|
- The current implementation is client-side only; no backend server or database is required.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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>
|
<title>Datacenter Modeler</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
8
public/favicon.svg
Normal file
8
public/favicon.svg
Normal 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 |
54
src/App.tsx
54
src/App.tsx
@@ -462,7 +462,11 @@ export default function App() {
|
|||||||
[...rack.equipment]
|
[...rack.equipment]
|
||||||
.sort((a, b) => b.uStart - a.uStart)
|
.sort((a, b) => b.uStart - a.uStart)
|
||||||
.forEach((item) => {
|
.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);
|
pdf.text(line.slice(0, 115), margin + 12, y);
|
||||||
y += 14;
|
y += 14;
|
||||||
});
|
});
|
||||||
@@ -949,6 +953,54 @@ function PropertiesPanel({
|
|||||||
onChange={(event) => onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, { assetTag: event.target.value })}
|
onChange={(event) => onEquipmentChange(selectedEquipmentRack.id, selectedEquipment.id, { assetTag: event.target.value })}
|
||||||
fullWidth
|
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
|
<TextField
|
||||||
label="Power watts"
|
label="Power watts"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export type ComponentType =
|
|||||||
|
|
||||||
export type LibraryCategory = 'container' | 'equipment';
|
export type LibraryCategory = 'container' | 'equipment';
|
||||||
|
|
||||||
|
export type PatchPanelMedium = '' | 'copper' | 'fiberoptic';
|
||||||
|
|
||||||
export interface LibraryItem {
|
export interface LibraryItem {
|
||||||
type: ComponentType;
|
type: ComponentType;
|
||||||
category: LibraryCategory;
|
category: LibraryCategory;
|
||||||
@@ -38,6 +40,9 @@ export interface Equipment {
|
|||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
assetTag: string;
|
assetTag: string;
|
||||||
powerWatts: number;
|
powerWatts: number;
|
||||||
|
patchPanelMedium: PatchPanelMedium;
|
||||||
|
patchPanelPorts: number;
|
||||||
|
patchPanelPortIdentification: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ export const createEquipment = (type: string, rack: RackContainer, preferredU: n
|
|||||||
serialNumber: '',
|
serialNumber: '',
|
||||||
assetTag: '',
|
assetTag: '',
|
||||||
powerWatts: 0,
|
powerWatts: 0,
|
||||||
|
patchPanelMedium: libraryItem.type === 'patch-panel' ? 'copper' : '',
|
||||||
|
patchPanelPorts: libraryItem.type === 'patch-panel' ? 24 : 0,
|
||||||
|
patchPanelPortIdentification: '',
|
||||||
notes: '',
|
notes: '',
|
||||||
color: libraryItem.color,
|
color: libraryItem.color,
|
||||||
};
|
};
|
||||||
@@ -150,6 +153,14 @@ export const normalizeDiagram = (value: unknown): DiagramData => {
|
|||||||
serialNumber: item.serialNumber || '',
|
serialNumber: item.serialNumber || '',
|
||||||
assetTag: item.assetTag || '',
|
assetTag: item.assetTag || '',
|
||||||
powerWatts: clampNumber(item.powerWatts, 0, 100000, 0),
|
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 || '',
|
notes: item.notes || '',
|
||||||
color: item.color || libraryItem.color,
|
color: item.color || libraryItem.color,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user