2023-05-17 04:09:05 +00:00
let subnetMap = { } ;
2023-05-19 07:02:46 +00:00
let subnetNotes = { } ;
2023-05-17 04:09:05 +00:00
let maxNetSize = 0 ;
let infoColumnCount = 5
2023-05-19 06:28:43 +00:00
// NORMAL mode:
// - Smallest subnet: /30
// - Two reserved addresses per subnet:
// - Network Address (network + 0)
// - Broadcast Address (last network address)
// AWS mode (future):
// - Smallest subnet: /28
// - Two reserved addresses per subnet:
// - Network Address (network + 0)
// - AWS Reserved - VPC Router
// - AWS Reserved - VPC DNS
// - AWS Reserved - Future Use
// - Broadcast Address (last network address)
let operatingMode = 'NORMAL'
2023-05-19 07:02:46 +00:00
let noteTimeout ;
2023-05-20 21:15:55 +00:00
let minSubnetSize = 30
2023-05-19 06:08:38 +00:00
$ ( 'input#network,input#netsize' ) . on ( 'input' , function ( ) {
$ ( '#input_form' ) [ 0 ] . classList . add ( 'was-validated' ) ;
} )
2023-05-17 04:09:05 +00:00
$ ( '#btn_go' ) . on ( 'click' , function ( ) {
2023-05-18 04:29:21 +00:00
reset ( ) ;
} )
$ ( '#btn_reset' ) . on ( 'click' , function ( ) {
reset ( ) ;
} )
function reset ( ) {
2023-05-20 21:15:55 +00:00
if ( operatingMode === 'AWS' ) {
minSubnetSize = 28
} else {
minSubnetSize = 30
}
2023-05-19 06:08:38 +00:00
let cidrInput = $ ( '#network' ) . val ( ) + '/' + $ ( '#netsize' ) . val ( )
let rootNetwork = get _network ( $ ( '#network' ) . val ( ) , $ ( '#netsize' ) . val ( ) )
let rootCidr = rootNetwork + '/' + $ ( '#netsize' ) . val ( )
if ( cidrInput !== rootCidr ) {
show _warning _modal ( '<div>Your network input is not on a network boundary for this network size. It has been automatically changed:</div><div class="font-monospace pt-2">' + $ ( '#network' ) . val ( ) + ' -> ' + rootNetwork + '</div>' )
}
$ ( '#network' ) . val ( rootNetwork )
2023-05-18 01:34:53 +00:00
subnetMap = { }
subnetMap [ rootCidr ] = { }
2023-05-17 04:09:05 +00:00
maxNetSize = parseInt ( $ ( '#netsize' ) . val ( ) )
2023-05-18 01:34:53 +00:00
/ *
2023-05-17 04:09:05 +00:00
subnetMap = {
'10.0.0.0/16' : {
'10.0.0.0/17' : { } ,
'10.0.128.0/17' : {
'10.0.128.0/18' : {
'10.0.128.0/19' : { } ,
'10.0.160.0/19' : {
'10.0.160.0/20' : { } ,
'10.0.176.0/20' : {
'10.0.176.0/21' : {
'10.0.176.0/22' : {
'10.0.176.0/23' : { } ,
'10.0.178.0/23' : { }
} ,
'10.0.180.0/22' : { }
} ,
'10.0.184.0/21' : { }
}
}
} ,
'10.0.192.0/18' : {
'10.0.192.0/19' : { } ,
'10.0.224.0/19' : {
'10.0.224.0/20' : { } ,
'10.0.240.0/20' : {
'10.0.240.0/21' : { } ,
'10.0.248.0/21' : {
'10.0.248.0/22' : { } ,
'10.0.252.0/22' : {
'10.0.252.0/23' : { } ,
'10.0.254.0/23' : {
'10.0.254.0/24' : { } ,
'10.0.255.0/24' : { }
}
}
}
}
}
}
}
}
}
2023-05-18 01:34:53 +00:00
* /
2023-05-17 04:09:05 +00:00
renderTable ( ) ;
2023-05-18 04:29:21 +00:00
}
2023-05-17 04:09:05 +00:00
2023-05-18 01:34:53 +00:00
$ ( '#calcbody' ) . on ( 'click' , 'td.split,td.join' , function ( event ) {
// HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags
mutate _subnet _map ( this . dataset . mutateVerb , this . dataset . subnet , subnetMap )
renderTable ( ) ;
2023-05-17 04:09:05 +00:00
} )
2023-05-19 07:02:46 +00:00
$ ( '#calcbody' ) . on ( 'keyup' , 'td.note input' , function ( event ) {
// HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags
let delay = 1000 ;
clearTimeout ( noteTimeout ) ;
noteTimeout = setTimeout ( function ( element ) {
console . log ( 'CAP' )
subnetNotes [ element . dataset . subnet ] = element . value
} , delay , this ) ;
} )
2023-05-19 07:15:07 +00:00
$ ( '#calcbody' ) . on ( 'focusout' , 'td.note input' , function ( event ) {
// HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags
clearTimeout ( noteTimeout ) ;
console . log ( 'CAP' )
subnetNotes [ this . dataset . subnet ] = this . value
} )
2023-05-19 07:02:46 +00:00
2023-05-17 04:09:05 +00:00
function renderTable ( ) {
// TODO: Validation Code
$ ( '#calcbody' ) . empty ( ) ;
let maxDepth = get _dict _max _depth ( subnetMap , 0 )
addRowTree ( subnetMap , 0 , maxDepth )
}
function addRowTree ( subnetTree , depth , maxDepth ) {
for ( let mapKey in subnetTree ) {
if ( Object . keys ( subnetTree [ mapKey ] ) . length > 0 ) {
addRowTree ( subnetTree [ mapKey ] , depth + 1 , maxDepth )
} else {
let subnet _split = mapKey . split ( '/' )
2023-05-18 00:36:32 +00:00
addRow ( subnet _split [ 0 ] , parseInt ( subnet _split [ 1 ] ) , ( infoColumnCount + maxDepth - depth ) )
2023-05-17 04:09:05 +00:00
}
}
}
2023-05-18 00:36:32 +00:00
function addRow ( network , netSize , colspan ) {
2023-05-17 04:09:05 +00:00
// TODO: do some checking here for smaller networks like /32, probably some edge cases to watch for.
let addressFirst = ip2int ( network )
let addressLast = subnet _last _address ( addressFirst , netSize )
// Will need to adjust this for AWS mode
let usableFirst = addressFirst + 1
let usableLast = addressLast - 1
let hostCount = 1 + usableLast - usableFirst
let newRow =
' <tr id="row_' + network . replace ( '.' , '-' ) + '_' + netSize + '">\n' +
' <td class="row_address">' + network + '/' + netSize + '</td>\n' +
' <td class="row_range">' + int2ip ( addressFirst ) + ' - ' + int2ip ( addressLast ) + '</td>\n' +
' <td class="row_usable">' + int2ip ( usableFirst ) + ' - ' + int2ip ( usableLast ) + '</td>\n' +
' <td class="row_hosts">' + hostCount + '</td>\n' +
2023-05-19 07:02:46 +00:00
' <td class="note"><label><input type="text" class="form-control shadow-none p-0" data-subnet="' + network + '/' + netSize + '" value="' + ( subnetNotes [ network + '/' + netSize ] || '' ) + '"></label></td>\n' +
2023-05-18 01:34:53 +00:00
' <td rowspan="1" colspan="' + colspan + '" class="split rotate" data-subnet="' + network + '/' + netSize + '" data-mutate-verb="split"><span>/' + netSize + '</span></td>\n'
2023-05-17 04:50:20 +00:00
if ( netSize > maxNetSize ) {
// This is wrong. Need to figure out a way to get the number of children so you can set rowspan and the number
2023-05-18 00:36:32 +00:00
// of ancestors so you can set colspan.
// DONE: If the subnet address (without the mask) matches a larger subnet address
2023-05-17 04:50:20 +00:00
// in the heirarchy that is a signal to add more join buttons to that row, since they start at the top row and
// via rowspan extend downward.
2023-05-18 00:36:32 +00:00
let matchingNetworkList = get _matching _network _list ( network , subnetMap ) . slice ( 1 )
for ( const i in matchingNetworkList ) {
let matchingNetwork = matchingNetworkList [ i ]
let networkChildrenCount = count _network _children ( matchingNetwork , subnetMap , [ ] )
2023-05-18 01:34:53 +00:00
newRow += ' <td rowspan="' + networkChildrenCount + '" colspan="1" class="join rotate" data-subnet="' + matchingNetwork + '" data-mutate-verb="join"><span>/' + matchingNetwork . split ( '/' ) [ 1 ] + '</span></td>\n'
2023-05-18 00:36:32 +00:00
}
2023-05-17 04:09:05 +00:00
}
newRow += ' </tr>' ;
$ ( '#calcbody' ) . append ( newRow )
}
// Helper Functions
function ip2int ( ip ) {
return ip . split ( '.' ) . reduce ( function ( ipInt , octet ) { return ( ipInt << 8 ) + parseInt ( octet , 10 ) } , 0 ) >>> 0 ;
}
function int2ip ( ipInt ) {
return ( ( ipInt >>> 24 ) + '.' + ( ipInt >> 16 & 255 ) + '.' + ( ipInt >> 8 & 255 ) + '.' + ( ipInt & 255 ) ) ;
}
function subnet _last _address ( subnet , netSize ) {
return subnet + subnet _addresses ( netSize ) - 1 ;
}
function subnet _addresses ( netSize ) {
return 2 * * ( 32 - netSize ) ;
}
function get _dict _max _depth ( dict , curDepth ) {
let maxDepth = curDepth
for ( let mapKey in dict ) {
let newDepth = get _dict _max _depth ( dict [ mapKey ] , curDepth + 1 )
if ( newDepth > maxDepth ) { maxDepth = newDepth }
}
return maxDepth
}
2023-05-17 04:50:20 +00:00
function get _join _children ( subnetTree , childCount ) {
for ( let mapKey in subnetTree ) {
if ( Object . keys ( subnetTree [ mapKey ] ) . length > 0 ) {
childCount += get _join _children ( subnetTree [ mapKey ] )
} else {
return childCount
}
}
2023-05-18 00:36:32 +00:00
}
function count _network _children ( network , subnetTree , ancestryList ) {
// TODO: This might be able to be optimized. Ultimately it needs to count the number of keys underneath
// the current key are unsplit networks (IE rows in the table, IE keys with a value of {}).
let childCount = 0
for ( let mapKey in subnetTree ) {
if ( Object . keys ( subnetTree [ mapKey ] ) . length > 0 ) {
childCount += count _network _children ( network , subnetTree [ mapKey ] , ancestryList . concat ( [ mapKey ] ) )
} else {
if ( ancestryList . includes ( network ) ) {
childCount += 1
}
}
}
return childCount
}
2023-05-20 21:15:55 +00:00
function get _network _children ( network , subnetTree ) {
// TODO: This might be able to be optimized. Ultimately it needs to count the number of keys underneath
// the current key are unsplit networks (IE rows in the table, IE keys with a value of {}).
let subnetList = [ ]
for ( let mapKey in subnetTree ) {
if ( Object . keys ( subnetTree [ mapKey ] ) . length > 0 ) {
subnetList . push . apply ( subnetList , get _network _children ( network , subnetTree [ mapKey ] ) )
} else {
subnetList . push ( mapKey )
}
}
return subnetList
}
2023-05-18 00:36:32 +00:00
function get _matching _network _list ( network , subnetTree ) {
let subnetList = [ ]
for ( let mapKey in subnetTree ) {
if ( Object . keys ( subnetTree [ mapKey ] ) . length > 0 ) {
subnetList . push . apply ( subnetList , get _matching _network _list ( network , subnetTree [ mapKey ] ) )
}
if ( mapKey . split ( '/' ) [ 0 ] === network ) {
subnetList . push ( mapKey )
}
}
return subnetList
2023-05-18 01:34:53 +00:00
}
function get _network ( networkInput , netSize ) {
let ipInt = ip2int ( networkInput )
netSize = parseInt ( netSize )
for ( let i = 31 - netSize ; i >= 0 ; i -- ) {
ipInt &= ~ 1 << i ;
}
return int2ip ( ipInt ) ;
}
function split _network ( networkInput , netSize ) {
let subnets = [ networkInput + '/' + ( netSize + 1 ) ]
let newSubnet = ip2int ( networkInput ) + 2 * * ( 32 - netSize - 1 ) ;
subnets . push ( int2ip ( newSubnet ) + '/' + ( netSize + 1 ) )
return subnets ;
}
function mutate _subnet _map ( verb , network , subnetTree ) {
for ( let mapKey in subnetTree ) {
if ( Object . keys ( subnetTree [ mapKey ] ) . length > 0 ) {
mutate _subnet _map ( verb , network , subnetTree [ mapKey ] )
}
if ( mapKey === network ) {
2023-05-20 21:15:55 +00:00
let netSplit = mapKey . split ( '/' )
let netSize = parseInt ( netSplit [ 1 ] )
2023-05-18 01:34:53 +00:00
if ( verb === 'split' ) {
2023-05-20 21:15:55 +00:00
if ( netSize < minSubnetSize ) {
let new _networks = split _network ( netSplit [ 0 ] , netSize )
2023-05-19 06:28:43 +00:00
subnetTree [ mapKey ] [ new _networks [ 0 ] ] = { }
subnetTree [ mapKey ] [ new _networks [ 1 ] ] = { }
2023-05-19 07:15:07 +00:00
// Copy note to both children and delete Delete parent note
subnetNotes [ new _networks [ 0 ] ] = subnetNotes [ mapKey ]
subnetNotes [ new _networks [ 1 ] ] = subnetNotes [ mapKey ]
delete subnetNotes [ mapKey ]
2023-05-19 06:28:43 +00:00
}
2023-05-18 01:34:53 +00:00
} else if ( verb === 'join' ) {
2023-05-19 07:15:07 +00:00
// Keep the note of the first subnet (which matches the network address) and lose the second subnet's note
// Could consider changing this to concatenate the notes into the parent, but I think this is more intuitive
2023-05-20 21:15:55 +00:00
// Find first (smallest) subnet note which matches the exact network address (this would be the top network in the join scope)
let smallestMatchingNetworkSize = 0
for ( let subnetCidr in subnetNotes ) {
if ( subnetCidr . startsWith ( netSplit [ 0 ] ) ) {
if ( parseInt ( subnetCidr . split ( '/' ) [ 1 ] ) > smallestMatchingNetworkSize ) {
smallestMatchingNetworkSize = subnetCidr . split ( '/' ) [ 1 ]
}
}
}
subnetNotes [ mapKey ] = subnetNotes [ netSplit [ 0 ] + '/' + smallestMatchingNetworkSize ]
// Delete all notes of subnets under this collapsed subnet
let removeKeys = get _network _children ( mapKey , subnetTree [ mapKey ] , [ ] )
for ( let removeKey in removeKeys ) {
subnetNotes [ removeKey ] = ''
}
// And delete the subnets themselves
2023-05-18 01:34:53 +00:00
subnetTree [ mapKey ] = { }
} else {
// How did you get here?
}
}
}
2023-05-18 04:29:21 +00:00
}
2023-05-19 06:08:38 +00:00
/ *
function validate _cidr ( network , netSize ) {
let returnObj = {
'valid' : false ,
'errorNetwork' : true ,
'errorSize' : true ,
'cidr' : false ,
'network' : false ,
'netSize' : false
}
returnObj [ 'network' ] = validate _network ( network )
if ( returnObj [ 'network' ] ) {
returnObj [ 'errorNetwork' ] = false ;
}
if ( ! /^\d+$/ . test ( netSize ) ) {
returnObj [ 'errorSize' ] = true ;
} else {
netSize = parseInt ( netSize )
if ( ( netSize > 32 ) || ( netSize < 0 ) ) {
returnObj [ 'errorSize' ] = true ;
} else {
returnObj [ 'errorSize' ] = false ;
returnObj [ 'netSize' ] = netSize . toString ( )
}
}
if ( ( returnObj [ 'errorNetwork' ] === false ) && ( returnObj [ 'errorSize' ] === false ) ) {
returnObj [ 'cidr' ] = returnObj [ 'network' ] + '/' + returnObj [ 'netSize' ]
returnObj [ 'valid' ] = true
}
return returnObj ;
}
function validate _network ( network ) {
// This can probably be done with Regex but this is better.
let octets = network . split ( '.' ) ;
if ( octets . length !== 4 ) { return false }
if ( ! /^\d+$/ . test ( octets [ 0 ] ) ) { return false }
if ( ! /^\d+$/ . test ( octets [ 1 ] ) ) { return false }
if ( ! /^\d+$/ . test ( octets [ 2 ] ) ) { return false }
if ( ! /^\d+$/ . test ( octets [ 3 ] ) ) { return false }
octets [ 0 ] = parseInt ( octets [ 0 ] )
octets [ 1 ] = parseInt ( octets [ 1 ] )
octets [ 2 ] = parseInt ( octets [ 2 ] )
octets [ 3 ] = parseInt ( octets [ 3 ] )
if ( ( octets [ 0 ] < 0 ) || ( octets [ 0 ] > 255 ) ) { return false }
if ( ( octets [ 1 ] < 0 ) || ( octets [ 1 ] > 255 ) ) { return false }
if ( ( octets [ 2 ] < 0 ) || ( octets [ 2 ] > 255 ) ) { return false }
if ( ( octets [ 3 ] < 0 ) || ( octets [ 3 ] > 255 ) ) { return false }
return octets . join ( '.' )
}
* /
function show _warning _modal ( message ) {
var notifyModal = new bootstrap . Modal ( document . getElementById ( "notifyModal" ) , { } ) ;
$ ( '#notifyModal .modal-body' ) . html ( message )
notifyModal . show ( )
}
2023-05-18 04:29:21 +00:00
$ ( document ) . ready ( function ( ) {
reset ( ) ;
2023-05-19 06:08:38 +00:00
} ) ;