@@ -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 = / ^ (?: (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) \. ) { 3 } (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 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 = / ^ (?: (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) \. ) { 3 } (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 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