Skip to content

Commit 2a051b0

Browse files
committed
feat: select diff ip on promote
1 parent 4c19852 commit 2a051b0

File tree

2 files changed

+157
-18
lines changed

2 files changed

+157
-18
lines changed

app.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,36 @@ def promote_lease():
253253
'error': 'ip_address and hw_address are required'
254254
}), 400
255255

256+
# Check if a different lease exists for this IP address
257+
try:
258+
leases = client.get_leases(subnet_id=subnet_id)
259+
existing_lease = next((l for l in leases if l.get('ip-address') == ip_address and l.get('hw-address') != hw_address), None)
260+
261+
if existing_lease:
262+
logger.warning(f"Cannot promote: lease already exists for IP {ip_address} with different MAC {existing_lease.get('hw-address')}")
263+
return jsonify({
264+
'success': False,
265+
'error': f'A lease already exists for IP {ip_address} with MAC address {existing_lease.get("hw-address")}. Please choose a different IP address.'
266+
}), 400
267+
except Exception as e:
268+
logger.warning(f"Could not verify existing leases: {e}")
269+
# Continue anyway if lease check fails
270+
271+
# Check if a reservation already exists for this IP
272+
try:
273+
reservations = client.get_reservations(subnet_id=subnet_id)
274+
existing_reservation = next((r for r in reservations if r.get('ip-address') == ip_address), None)
275+
276+
if existing_reservation:
277+
logger.warning(f"Cannot promote: reservation already exists for IP {ip_address}")
278+
return jsonify({
279+
'success': False,
280+
'error': f'A reservation already exists for IP {ip_address}. Please choose a different IP address.'
281+
}), 400
282+
except Exception as e:
283+
logger.warning(f"Could not verify existing reservations: {e}")
284+
# Continue anyway if reservation check fails
285+
256286
logger.info(f"Promoting lease: IP={ip_address}, MAC={hw_address}")
257287

258288
result = client.create_reservation(

templates/index.html

Lines changed: 127 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -152,25 +152,36 @@ <h5 class="modal-title">Promote Lease to Reservation</h5>
152152
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
153153
</div>
154154
<div class="modal-body">
155-
<p>Are you sure you want to promote this lease to a permanent reservation?</p>
156-
<dl class="row">
157-
<dt class="col-sm-4">IP Address:</dt>
158-
<dd class="col-sm-8" id="modal-ip"></dd>
159-
<dt class="col-sm-4">MAC Address:</dt>
160-
<dd class="col-sm-8" id="modal-mac"></dd>
161-
<dt class="col-sm-4">Hostname:</dt>
162-
<dd class="col-sm-8" id="modal-hostname"></dd>
163-
<dt class="col-sm-4">Subnet ID:</dt>
164-
<dd class="col-sm-8" id="modal-subnet"></dd>
165-
</dl>
155+
<p>Configure the permanent reservation for this device:</p>
156+
<form id="promote-form">
157+
<div class="mb-3">
158+
<label for="promote-ip-address" class="form-label">IP Address *</label>
159+
<input type="text" class="form-control" id="promote-ip-address"
160+
placeholder="192.168.1.100" required>
161+
<div class="form-text">You can change the IP address. Defaults to the lease's current IP.</div>
162+
<div id="ip-validation-message" class="mt-2" style="display: none;"></div>
163+
</div>
164+
<div class="mb-3">
165+
<label class="form-label">MAC Address</label>
166+
<input type="text" class="form-control" id="promote-mac-address" readonly>
167+
</div>
168+
<div class="mb-3">
169+
<label class="form-label">Hostname</label>
170+
<div id="promote-hostname" class="form-control-plaintext"></div>
171+
</div>
172+
<div class="mb-3">
173+
<label class="form-label">Subnet ID</label>
174+
<div id="promote-subnet" class="form-control-plaintext"></div>
175+
</div>
176+
</form>
166177
<div class="alert alert-info mb-0">
167178
<i class="bi bi-info-circle"></i>
168179
This will create a permanent reservation for this device.
169180
</div>
170181
</div>
171182
<div class="modal-footer">
172183
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
173-
<button type="button" class="btn btn-success" onclick="confirmPromotion()">
184+
<button type="button" class="btn btn-success" onclick="confirmPromotion()" id="confirm-promote-btn">
174185
<i class="bi bi-check-circle"></i> Confirm Promotion
175186
</button>
176187
</div>
@@ -705,25 +716,120 @@ <h6 class="border-bottom pb-2 mb-3 mt-4">Logging Settings</h6>
705716
function promoteLease(lease) {
706717
selectedLease = lease;
707718

708-
document.getElementById('modal-ip').textContent = lease['ip-address'];
709-
document.getElementById('modal-mac').textContent = lease['hw-address'];
710-
document.getElementById('modal-hostname').textContent = lease.hostname || 'none';
711-
document.getElementById('modal-subnet').textContent = lease['subnet-id'];
719+
// Populate form with lease data
720+
document.getElementById('promote-ip-address').value = lease['ip-address'];
721+
document.getElementById('promote-mac-address').value = lease['hw-address'];
722+
document.getElementById('promote-hostname').textContent = lease.hostname || 'none';
723+
document.getElementById('promote-subnet').textContent = lease['subnet-id'];
724+
725+
// Clear any previous validation messages
726+
const validationMsg = document.getElementById('ip-validation-message');
727+
validationMsg.style.display = 'none';
728+
validationMsg.className = '';
729+
730+
// Enable the confirm button
731+
document.getElementById('confirm-promote-btn').disabled = false;
732+
733+
// Add IP validation on change
734+
const ipInput = document.getElementById('promote-ip-address');
735+
ipInput.removeEventListener('input', validatePromoteIP);
736+
ipInput.addEventListener('input', validatePromoteIP);
712737

713738
promoteModal.show();
714739
}
740+
741+
async function validatePromoteIP() {
742+
const ipInput = document.getElementById('promote-ip-address');
743+
const newIP = ipInput.value.trim();
744+
const originalIP = selectedLease['ip-address'];
745+
const validationMsg = document.getElementById('ip-validation-message');
746+
const confirmBtn = document.getElementById('confirm-promote-btn');
747+
748+
// If IP hasn't changed, no validation needed
749+
if (newIP === originalIP) {
750+
validationMsg.style.display = 'none';
751+
confirmBtn.disabled = false;
752+
return;
753+
}
754+
755+
// Basic IP format validation
756+
const ipPattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
757+
if (!ipPattern.test(newIP)) {
758+
validationMsg.className = 'alert alert-warning';
759+
validationMsg.innerHTML = '<i class="bi bi-exclamation-triangle"></i> Please enter a valid IP address';
760+
validationMsg.style.display = 'block';
761+
confirmBtn.disabled = true;
762+
return;
763+
}
764+
765+
// Check if another lease exists for this IP
766+
const existingLease = allLeases.find(l => l['ip-address'] === newIP);
767+
768+
if (existingLease) {
769+
validationMsg.className = 'alert alert-danger';
770+
validationMsg.innerHTML = `<i class="bi bi-exclamation-circle"></i> <strong>Conflict!</strong> A lease already exists for IP ${newIP}` +
771+
`<br><small>MAC: ${existingLease['hw-address']}${existingLease.hostname ? ', Hostname: ' + existingLease.hostname : ''}</small>`;
772+
validationMsg.style.display = 'block';
773+
confirmBtn.disabled = true;
774+
return;
775+
}
776+
777+
// Check if a reservation exists for this IP
778+
const existingReservation = allReservations.find(r => r['ip-address'] === newIP);
779+
780+
if (existingReservation) {
781+
validationMsg.className = 'alert alert-danger';
782+
validationMsg.innerHTML = `<i class="bi bi-exclamation-circle"></i> <strong>Conflict!</strong> A reservation already exists for IP ${newIP}` +
783+
`<br><small>MAC: ${existingReservation['hw-address']}${existingReservation.hostname ? ', Hostname: ' + existingReservation.hostname : ''}</small>`;
784+
validationMsg.style.display = 'block';
785+
confirmBtn.disabled = true;
786+
return;
787+
}
788+
789+
// IP is valid and available
790+
validationMsg.className = 'alert alert-success';
791+
validationMsg.innerHTML = '<i class="bi bi-check-circle"></i> IP address is available';
792+
validationMsg.style.display = 'block';
793+
confirmBtn.disabled = false;
794+
}
715795

716796
async function confirmPromotion() {
717797
if (!selectedLease) return;
718798

799+
// Get the IP address from the input field
800+
const ipAddress = document.getElementById('promote-ip-address').value.trim();
801+
const originalIP = selectedLease['ip-address'];
802+
803+
// Validate IP format
804+
const ipPattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
805+
if (!ipPattern.test(ipAddress)) {
806+
alert('Please enter a valid IP address');
807+
return;
808+
}
809+
810+
// If IP changed, do final validation
811+
if (ipAddress !== originalIP) {
812+
const existingLease = allLeases.find(l => l['ip-address'] === ipAddress);
813+
if (existingLease) {
814+
alert(`Cannot create reservation: A lease already exists for IP ${ipAddress}`);
815+
return;
816+
}
817+
818+
const existingReservation = allReservations.find(r => r['ip-address'] === ipAddress);
819+
if (existingReservation) {
820+
alert(`Cannot create reservation: A reservation already exists for IP ${ipAddress}`);
821+
return;
822+
}
823+
}
824+
719825
try {
720826
const response = await fetch('/api/promote', {
721827
method: 'POST',
722828
headers: {
723829
'Content-Type': 'application/json'
724830
},
725831
body: JSON.stringify({
726-
ip_address: selectedLease['ip-address'],
832+
ip_address: ipAddress,
727833
hw_address: selectedLease['hw-address'],
728834
hostname: selectedLease.hostname || '',
729835
subnet_id: selectedLease['subnet-id']
@@ -736,7 +842,10 @@ <h6 class="border-bottom pb-2 mb-3 mt-4">Logging Settings</h6>
736842
promoteModal.hide();
737843

738844
// Show success message
739-
showAlert('success', data.message);
845+
const successMsg = ipAddress !== originalIP
846+
? `Successfully promoted lease to reservation with new IP ${ipAddress} (was ${originalIP})`
847+
: data.message;
848+
showAlert('success', successMsg);
740849

741850
// Refresh leases and reservations
742851
await loadReservations();

0 commit comments

Comments
 (0)