feat(rack-planner): add support for WAN and SFP ports with right-justified layout and distinct styling

This commit is contained in:
2026-03-22 15:16:54 -05:00
parent b26f88a89e
commit 1f360cdb2a
6 changed files with 162 additions and 59 deletions
+45 -16
View File
@@ -35,6 +35,8 @@ export function AddModuleModal({ open, onClose, rackId, uPosition, initialType }
const [ipAddress, setIpAddress] = useState('');
const [manufacturer, setManufacturer] = useState('');
const [model, setModel] = useState('');
const [sfpCount, setSfpCount] = useState(0);
const [wanCount, setWanCount] = useState(0);
const [loading, setLoading] = useState(false);
// Sync state when modal opens with a new initialType (e.g. drag-drop reuse)
@@ -54,6 +56,8 @@ export function AddModuleModal({ open, onClose, rackId, uPosition, initialType }
setName(MODULE_TYPE_LABELS[type]);
setUSize(MODULE_U_DEFAULTS[type]);
setPortCount(MODULE_PORT_DEFAULTS[type]);
setSfpCount(0);
setWanCount(0);
}
function reset() {
@@ -64,6 +68,8 @@ export function AddModuleModal({ open, onClose, rackId, uPosition, initialType }
setIpAddress('');
setManufacturer('');
setModel('');
setSfpCount(0);
setWanCount(0);
}
async function handleSubmit(e: FormEvent) {
@@ -77,6 +83,8 @@ export function AddModuleModal({ open, onClose, rackId, uPosition, initialType }
uPosition,
uSize,
portCount,
sfpCount,
wanCount,
ipAddress: ipAddress.trim() || undefined,
manufacturer: manufacturer.trim() || undefined,
model: model.trim() || undefined,
@@ -165,31 +173,17 @@ export function AddModuleModal({ open, onClose, rackId, uPosition, initialType }
/>
</div>
<div className="grid grid-cols-3 gap-3">
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="block text-sm text-slate-300">Size (U)</label>
<input
type="number"
min={1}
max={20}
type="number" min={1} max={20}
value={uSize}
onChange={(e) => setUSize(Number(e.target.value))}
disabled={loading}
className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
/>
</div>
<div className="space-y-1">
<label className="block text-sm text-slate-300">Port count</label>
<input
type="number"
min={0}
max={128}
value={portCount}
onChange={(e) => setPortCount(Number(e.target.value))}
disabled={loading}
className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
/>
</div>
<div className="space-y-1">
<label className="block text-sm text-slate-300">IP Address</label>
<input
@@ -202,6 +196,41 @@ export function AddModuleModal({ open, onClose, rackId, uPosition, initialType }
</div>
</div>
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1">
<label className="block text-sm text-slate-300">Ethernet</label>
<input
type="number" min={0} max={128}
value={portCount}
onChange={(e) => setPortCount(Number(e.target.value))}
disabled={loading}
className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
/>
</div>
<div className="space-y-1">
<label className="block text-sm text-slate-300">SFP</label>
<input
type="number" min={0} max={128}
value={sfpCount}
onChange={(e) => setSfpCount(Number(e.target.value))}
disabled={loading}
className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
/>
</div>
<div className="space-y-1">
<label className="block text-sm text-slate-300">WAN</label>
<input
type="number" min={0} max={128}
value={wanCount}
onChange={(e) => setWanCount(Number(e.target.value))}
disabled={loading}
className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="block text-sm text-slate-300">Manufacturer</label>