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:
2023-06-05 20:20:24 +00:00
// - Smallest subnet: /32
// - Two reserved addresses per subnet of size <= 30:
2024-06-24 17:07:26 +00:00
// - Net+0 = Network Address
// - Last = Broadcast Address
// AWS mode:
2023-05-19 06:28:43 +00:00
// - Smallest subnet: /28
// - Two reserved addresses per subnet:
2024-06-24 17:07:26 +00:00
// - Net+0 = Network Address
// - Net+1 = AWS Reserved - VPC Router
// - Net+2 = AWS Reserved - VPC DNS
// - Net+3 = AWS Reserved - Future Use
// - Last = Broadcast Address
// Azure mode:
2024-06-24 15:52:29 +00:00
// - Smallest subnet: /29
// - Two reserved addresses per subnet:
2024-06-24 17:07:26 +00:00
// - Net+0 = Network Address
// - Net+1 = Reserved - Default Gateway
// - Net+2 = Reserved - DNS Mapping
// - Net+3 = Reserved - DNS Mapping
// - Last = Broadcast Address
2023-05-19 07:02:46 +00:00
let noteTimeout ;
2024-06-24 15:52:29 +00:00
let operatingMode = 'Standard'
let previousOperatingMode = 'Standard'
2023-05-23 05:15:37 +00:00
let inflightColor = 'NONE'
2024-06-24 17:55:02 +00:00
let urlVersion = '1'
2024-10-08 07:24:38 +00:00
let configVersion = '2'
2024-06-24 15:52:29 +00:00
const netsizePatterns = {
2024-10-08 07:20:19 +00:00
Standard : '^([12]?[0-9]|3[0-2])$' ,
AZURE : '^([12]?[0-9])$' ,
AWS : '^(1?[0-9]|2[0-8])$' ,
2024-06-24 15:52:29 +00:00
} ;
const minSubnetSizes = {
Standard : 32 ,
AZURE : 29 ,
AWS : 28 ,
} ;
2024-10-13 22:19:09 +00:00
$ ( 'input#network' ) . on ( 'paste' , function ( e ) {
let pastedData = window . event . clipboardData . getData ( 'text' )
if ( pastedData . includes ( '/' ) ) {
let [ network , netSize ] = pastedData . split ( '/' )
$ ( '#network' ) . val ( network )
$ ( '#netsize' ) . val ( netSize )
}
e . preventDefault ( )
} ) ;
$ ( "input#network" ) . on ( 'keydown' , function ( e ) {
if ( e . key === '/' ) {
e . preventDefault ( )
$ ( 'input#netsize' ) . focus ( ) . select ( )
}
} ) ;
2023-05-19 06:08:38 +00:00
$ ( 'input#network,input#netsize' ) . on ( 'input' , function ( ) {
$ ( '#input_form' ) [ 0 ] . classList . add ( 'was-validated' ) ;
} )
2023-05-22 06:42:43 +00:00
$ ( '#color_palette div' ) . on ( 'click' , function ( ) {
2023-05-23 07:38:41 +00:00
// We don't really NEED to convert this to hex, but it's really low overhead to do the
// conversion here and saves us space in the export/save
inflightColor = rgba2hex ( $ ( this ) . css ( 'background-color' ) )
2023-05-22 06:42:43 +00:00
} )
2023-05-23 07:38:41 +00:00
$ ( '#calcbody' ) . on ( 'click' , '.row_address, .row_range, .row_usable, .row_hosts, .note, input' , function ( event ) {
2023-05-23 05:15:37 +00:00
if ( inflightColor !== 'NONE' ) {
2023-05-23 07:38:41 +00:00
mutate _subnet _map ( 'color' , this . dataset . subnet , '' , inflightColor )
// We could re-render here, but there is really no point, keep performant and just change the background color now
//renderTable();
2023-06-05 22:19:14 +00:00
$ ( this ) . closest ( 'tr' ) . css ( 'background-color' , inflightColor )
2023-05-23 05:15:37 +00:00
}
2023-05-22 06:42:43 +00:00
} )
2023-05-17 04:09:05 +00:00
$ ( '#btn_go' ) . on ( 'click' , function ( ) {
2024-06-24 15:52:29 +00:00
$ ( '#input_form' ) . removeClass ( 'was-validated' ) ;
$ ( '#input_form' ) . validate ( ) ;
if ( $ ( '#input_form' ) . valid ( ) ) {
$ ( '#input_form' ) [ 0 ] . classList . add ( 'was-validated' ) ;
reset ( ) ;
// Additional actions upon validation can be added here
} else {
show _warning _modal ( '<div>Please correct the errors in the form!</div>' ) ;
}
2023-05-18 04:29:21 +00:00
} )
2024-06-24 15:52:29 +00:00
$ ( '#dropdown_standard' ) . click ( function ( ) {
previousOperatingMode = operatingMode ;
operatingMode = 'Standard' ;
if ( ! switchMode ( operatingMode ) ) {
operatingMode = previousOperatingMode ;
2024-06-24 17:07:26 +00:00
$ ( '#dropdown_' + operatingMode . toLowerCase ( ) ) . addClass ( 'active' ) ;
2024-06-24 15:52:29 +00:00
}
} ) ;
$ ( '#dropdown_azure' ) . click ( function ( ) {
previousOperatingMode = operatingMode ;
operatingMode = 'AZURE' ;
if ( ! switchMode ( operatingMode ) ) {
operatingMode = previousOperatingMode ;
2024-06-24 17:07:26 +00:00
$ ( '#dropdown_' + operatingMode . toLowerCase ( ) ) . addClass ( 'active' ) ;
2024-06-24 15:52:29 +00:00
}
} ) ;
$ ( '#dropdown_aws' ) . click ( function ( ) {
previousOperatingMode = operatingMode ;
operatingMode = 'AWS' ;
if ( ! switchMode ( operatingMode ) ) {
operatingMode = previousOperatingMode ;
2024-06-24 17:07:26 +00:00
$ ( '#dropdown_' + operatingMode . toLowerCase ( ) ) . addClass ( 'active' ) ;
2024-06-24 15:52:29 +00:00
}
} ) ;
2023-05-21 06:05:19 +00:00
$ ( '#importBtn' ) . on ( 'click' , function ( ) {
2023-05-25 06:35:45 +00:00
importConfig ( JSON . parse ( $ ( '#importExportArea' ) . val ( ) ) )
2023-05-21 06:05:19 +00:00
} )
2023-05-23 05:15:37 +00:00
$ ( '#bottom_nav #colors_word_open' ) . on ( 'click' , function ( ) {
$ ( '#bottom_nav #color_palette' ) . removeClass ( 'd-none' ) ;
$ ( '#bottom_nav #colors_word_close' ) . removeClass ( 'd-none' ) ;
$ ( '#bottom_nav #colors_word_open' ) . addClass ( 'd-none' ) ;
} )
$ ( '#bottom_nav #colors_word_close' ) . on ( 'click' , function ( ) {
$ ( '#bottom_nav #color_palette' ) . addClass ( 'd-none' ) ;
$ ( '#bottom_nav #colors_word_close' ) . addClass ( 'd-none' ) ;
$ ( '#bottom_nav #colors_word_open' ) . removeClass ( 'd-none' ) ;
inflightColor = 'NONE'
} )
2023-05-25 06:35:45 +00:00
$ ( '#bottom_nav #copy_url' ) . on ( 'click' , function ( ) {
// TODO: Provide a warning here if the URL is longer than 2000 characters, probably using a modal.
let url = window . location . origin + getConfigUrl ( )
navigator . clipboard . writeText ( url ) ;
$ ( '#bottom_nav #copy_url span' ) . text ( 'Copied!' )
// Swap the text back after 3sec
setTimeout ( function ( ) {
$ ( '#bottom_nav #copy_url span' ) . text ( 'Copy Shareable URL' )
} , 2000 )
} )
2023-05-21 06:05:19 +00:00
$ ( '#btn_import_export' ) . on ( 'click' , function ( ) {
2024-10-08 07:24:38 +00:00
$ ( '#importExportArea' ) . val ( JSON . stringify ( exportConfig ( false ) , null , 2 ) )
2023-05-18 04:29:21 +00:00
} )
function reset ( ) {
2024-06-24 17:07:26 +00:00
set _usable _ips _title ( operatingMode ) ;
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>' )
2024-10-13 21:54:59 +00:00
$ ( '#network' ) . val ( rootNetwork )
cidrInput = $ ( '#network' ) . val ( ) + '/' + $ ( '#netsize' ) . val ( )
}
if ( Object . keys ( subnetMap ) . length > 0 ) {
// This page already has data imported, so lets see if we can just change the range
if ( isMatchingSize ( Object . keys ( subnetMap ) [ 0 ] , cidrInput ) ) {
subnetMap = changeBaseNetwork ( cidrInput )
} else {
// This is a page with existing data of a different subnet size, so make it blank
// Could be an opportunity here to do the following:
// - Prompt the user to confirm they want to clear the existing data
// - Resize the existing data anyway by making the existing network a subnetwork of their new input (if it
// is a larger network), or by just trimming the network to the new size (if it is a smaller network),
// or even resizing all of the containing networks by change in size of the base network. For example a
// base network going from /16 -> /18 would be all containing networks would be resized smaller (/+2),
// or bigger (/-2) if going from /18 -> /16.
subnetMap = { }
subnetMap [ rootCidr ] = { }
}
} else {
// This is a fresh page load with no existing data
subnetMap [ rootCidr ] = { }
2023-05-19 06:08:38 +00:00
}
2023-05-17 04:09:05 +00:00
maxNetSize = parseInt ( $ ( '#netsize' ) . val ( ) )
2024-06-24 15:52:29 +00:00
renderTable ( operatingMode ) ;
2023-05-18 04:29:21 +00:00
}
2023-05-17 04:09:05 +00:00
2024-10-13 21:54:59 +00:00
function changeBaseNetwork ( newBaseNetwork ) {
// Minifiy it, to make all the keys in the subnetMap relative to their original base network
// Then expand it, but with the new CIDR as the base network, effectively converting from old to new.
let miniSubnetMap = { }
minifySubnetMap ( miniSubnetMap , subnetMap , Object . keys ( subnetMap ) [ 0 ] )
let newSubnetMap = { }
expandSubnetMap ( newSubnetMap , miniSubnetMap , newBaseNetwork )
return newSubnetMap
}
function isMatchingSize ( subnet1 , subnet2 ) {
return subnet1 . split ( '/' ) [ 1 ] === subnet2 . split ( '/' ) [ 1 ] ;
}
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
2023-05-23 07:38:41 +00:00
mutate _subnet _map ( this . dataset . mutateVerb , this . dataset . subnet , '' )
2024-06-24 15:52:29 +00:00
renderTable ( operatingMode ) ;
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 ) {
2023-05-23 07:38:41 +00:00
mutate _subnet _map ( 'note' , element . dataset . subnet , '' , element . value )
2023-05-19 07:02:46 +00:00
} , 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 ) ;
2023-05-23 07:38:41 +00:00
mutate _subnet _map ( 'note' , this . dataset . subnet , '' , this . value )
2023-05-19 07:15:07 +00:00
} )
2023-05-19 07:02:46 +00:00
2024-06-24 15:52:29 +00:00
function renderTable ( operatingMode ) {
2023-05-17 04:09:05 +00:00
// TODO: Validation Code
$ ( '#calcbody' ) . empty ( ) ;
let maxDepth = get _dict _max _depth ( subnetMap , 0 )
2024-06-24 15:52:29 +00:00
addRowTree ( subnetMap , 0 , maxDepth , operatingMode )
2023-05-17 04:09:05 +00:00
}
2024-06-24 15:52:29 +00:00
function addRowTree ( subnetTree , depth , maxDepth , operatingMode ) {
2023-05-17 04:09:05 +00:00
for ( let mapKey in subnetTree ) {
2023-05-23 07:38:41 +00:00
if ( mapKey . startsWith ( '_' ) ) { continue ; }
if ( has _network _sub _keys ( subnetTree [ mapKey ] ) ) {
2024-06-24 15:52:29 +00:00
addRowTree ( subnetTree [ mapKey ] , depth + 1 , maxDepth , operatingMode )
2023-05-17 04:09:05 +00:00
} else {
let subnet _split = mapKey . split ( '/' )
2023-05-22 06:42:43 +00:00
let notesWidth = '30%' ;
if ( ( maxDepth > 5 ) && ( maxDepth <= 10 ) ) {
notesWidth = '25%' ;
} else if ( ( maxDepth > 10 ) && ( maxDepth <= 15 ) ) {
notesWidth = '20%' ;
} else if ( ( maxDepth > 15 ) && ( maxDepth <= 20 ) ) {
notesWidth = '15%' ;
} else if ( maxDepth > 20 ) {
notesWidth = '10%' ;
}
2024-06-24 15:52:29 +00:00
addRow ( subnet _split [ 0 ] , parseInt ( subnet _split [ 1 ] ) , ( infoColumnCount + maxDepth - depth ) , ( subnetTree [ mapKey ] [ '_note' ] || '' ) , notesWidth , ( subnetTree [ mapKey ] [ '_color' ] || '' ) , operatingMode )
2023-05-17 04:09:05 +00:00
}
}
}
2024-06-24 15:52:29 +00:00
function addRow ( network , netSize , colspan , note , notesWidth , color , operatingMode ) {
2023-05-17 04:09:05 +00:00
let addressFirst = ip2int ( network )
let addressLast = subnet _last _address ( addressFirst , netSize )
2023-06-05 20:20:24 +00:00
let usableFirst = subnet _usable _first ( addressFirst , netSize , operatingMode )
let usableLast = subnet _usable _last ( addressFirst , netSize )
2023-05-17 04:09:05 +00:00
let hostCount = 1 + usableLast - usableFirst
2023-05-23 07:38:41 +00:00
let styleTag = ''
if ( color !== '' ) {
styleTag = ' style="background-color: ' + color + '"'
}
2023-06-05 20:20:24 +00:00
let rangeCol , usableCol ;
if ( netSize < 32 ) {
rangeCol = int2ip ( addressFirst ) + ' - ' + int2ip ( addressLast ) ;
usableCol = int2ip ( usableFirst ) + ' - ' + int2ip ( usableLast ) ;
} else {
rangeCol = int2ip ( addressFirst ) ;
usableCol = int2ip ( usableFirst ) ;
}
2024-10-14 04:58:25 +00:00
let rowId = 'row_' + network . replace ( '.' , '-' ) + '_' + netSize
let rowCIDR = network + '/' + netSize
2023-05-17 04:09:05 +00:00
let newRow =
2024-10-14 04:58:25 +00:00
' <tr id="' + rowId + '"' + styleTag + ' aria-label="' + rowCIDR + '">\n' +
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' subnetHeader" class="row_address">' + rowCIDR + '</td>\n' +
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' rangeHeader" class="row_range">' + rangeCol + '</td>\n' +
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' useableHeader" class="row_usable">' + usableCol + '</td>\n' +
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' hostsHeader" class="row_hosts">' + hostCount + '</td>\n' +
' <td aria-labelledby="' + rowId + ' noteHeader" class="note" style="width:' + notesWidth + '"><label><input type="text" class="form-control shadow-none p-0" data-subnet="' + rowCIDR + '" value="' + note + '"></label></td>\n' +
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' splitHeader" rowspan="1" colspan="' + colspan + '" class="split rotate" 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 , [ ] )
2024-10-14 04:58:25 +00:00
newRow += ' <td aria-label="' + matchingNetwork + ' Join" 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 ) {
2024-10-08 07:24:38 +00:00
return ( ( ipInt >>> 24 ) + '.' + ( ipInt >> 16 & 255 ) + '.' + ( ipInt >> 8 & 255 ) + '.' + ( ipInt & 255 ) ) ;
}
2024-10-13 22:56:26 +00:00
function toBase36 ( num ) {
return num . toString ( 36 ) ;
2024-10-08 07:24:38 +00:00
}
2024-10-13 22:56:26 +00:00
function fromBase36 ( str ) {
return parseInt ( str , 36 ) ;
2024-10-08 07:24:38 +00:00
}
/ * *
* Coordinate System for Subnet Representation
*
* This system aims to represent subnets efficiently within a larger network space .
* The goal is to produce the shortest possible string representation for subnets ,
* which is particularly effective when dealing with hierarchical network designs .
*
* Key concept :
* - We represent a subnet by its ordinal position within a larger network ,
* along with its mask size .
* - This approach is most efficient when subnets are relatively close together
* in the address space and of similar sizes .
*
* Benefits :
* 1. Compact representation : Often results in very short strings ( e . g . , "7k" ) .
* 2. Hierarchical : Naturally represents subnet hierarchy .
* 3. Efficient for common cases : Works best for typical network designs where
* subnets are grouped and of similar sizes .
*
* Trade - offs :
* - Less efficient for representing widely dispersed or highly varied subnet sizes .
* - Requires knowledge of the base network to interpret .
*
* Extreme Example ... Representing the value 192.168 . 200.210 / 31 within the base
* network of 192.168 . 200.192 / 27. These are arbitrary but long subnets to represent
* as a string .
* - Normal Way - '192.168.200.210/31'
* - Nth Position Way - '9v'
* - '9' represents the 9 th / 31 subnet within the / 27
2024-10-13 22:56:26 +00:00
* - 'v' represents the / 31 mask size converted to Base 36 ( 31 - > 'v' )
2024-10-08 07:24:38 +00:00
* /
/ * *
* Converts a specific subnet to its Nth position representation within a base network .
*
* @ param { string } baseNetwork - The larger network containing the subnet ( e . g . , "10.0.0.0/16" )
* @ param { string } specificSubnet - The subnet to be represented ( e . g . , "10.0.112.0/20" )
* @ returns { string } A compact string representing the subnet ' s position and size ( e . g . , "7k" )
* /
function getNthSubnet ( baseNetwork , specificSubnet ) {
const [ baseIp , baseMask ] = baseNetwork . split ( '/' ) ;
const [ specificIp , specificMask ] = specificSubnet . split ( '/' ) ;
const baseInt = ip2int ( baseIp ) ;
const specificInt = ip2int ( specificIp ) ;
const baseSize = 32 - parseInt ( baseMask , 10 ) ;
const specificSize = 32 - parseInt ( specificMask , 10 ) ;
const offset = specificInt - baseInt ;
const nthSubnet = offset >>> specificSize ;
2024-10-13 22:56:26 +00:00
return ` ${ nthSubnet } ${ toBase36 ( parseInt ( specificMask , 10 ) ) } ` ;
2024-10-08 07:24:38 +00:00
}
/ * *
* Reconstructs a subnet from its Nth position representation within a base network .
*
* @ param { string } baseNetwork - The larger network containing the subnet ( e . g . , "10.0.0.0/16" )
* @ param { string } nthString - The compact representation of the subnet ( e . g . , "7k" )
* @ returns { string } The full subnet representation ( e . g . , "10.0.112.0/20" )
* /
// Takes 10.0.0.0/16 and '7k' and returns 10.0.96.0/20
2024-10-13 22:56:26 +00:00
// '10.0.96.0/20' being the 7th /20 (base36 'k' is 20 int) within the /16.
2024-10-08 07:24:38 +00:00
function getSubnetFromNth ( baseNetwork , nthString ) {
const [ baseIp , baseMask ] = baseNetwork . split ( '/' ) ;
const baseInt = ip2int ( baseIp ) ;
2024-10-13 22:56:26 +00:00
const size = fromBase36 ( nthString . slice ( - 1 ) ) ;
2024-10-08 07:24:38 +00:00
const nth = parseInt ( nthString . slice ( 0 , - 1 ) , 10 ) ;
const innerSizeInt = 32 - size ;
const subnetInt = baseInt + ( nth << innerSizeInt ) ;
return ` ${ int2ip ( subnetInt ) } / ${ size } ` ;
2023-05-17 04:09:05 +00:00
}
function subnet _last _address ( subnet , netSize ) {
return subnet + subnet _addresses ( netSize ) - 1 ;
}
function subnet _addresses ( netSize ) {
return 2 * * ( 32 - netSize ) ;
}
2023-06-05 20:20:24 +00:00
function subnet _usable _first ( network , netSize , operatingMode ) {
if ( netSize < 31 ) {
// https://docs.aws.amazon.com/vpc/latest/userguide/subnet-sizing.html
// AWS reserves 3 additional IPs
2024-06-24 15:52:29 +00:00
// https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets
// Azure reserves 3 additional IPs
return network + ( operatingMode == 'Standard' ? 1 : 4 ) ;
2023-06-05 20:20:24 +00:00
} else {
return network ;
}
}
function subnet _usable _last ( network , netSize ) {
let last _address = subnet _last _address ( network , netSize ) ;
if ( netSize < 31 ) {
return last _address - 1 ;
} else {
return last _address ;
}
}
2023-05-17 04:09:05 +00:00
function get _dict _max _depth ( dict , curDepth ) {
let maxDepth = curDepth
for ( let mapKey in dict ) {
2023-05-23 07:38:41 +00:00
if ( mapKey . startsWith ( '_' ) ) { continue ; }
2023-05-17 04:09:05 +00:00
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 ) {
2023-05-23 07:38:41 +00:00
if ( mapKey . startsWith ( '_' ) ) { continue ; }
if ( has _network _sub _keys ( subnetTree [ mapKey ] ) ) {
2023-05-17 04:50:20 +00:00
childCount += get _join _children ( subnetTree [ mapKey ] )
} else {
return childCount
}
}
2023-05-18 00:36:32 +00:00
}
2023-05-23 07:38:41 +00:00
function has _network _sub _keys ( dict ) {
let allKeys = Object . keys ( dict )
// Maybe an efficient way to do this with a Lambda?
for ( let i in allKeys ) {
2024-10-08 07:24:38 +00:00
if ( ! allKeys [ i ] . startsWith ( '_' ) && allKeys [ i ] !== 'n' && allKeys [ i ] !== 'c' ) {
2023-05-23 07:38:41 +00:00
return true
}
}
return false
}
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 ) {
2023-05-23 07:38:41 +00:00
if ( mapKey . startsWith ( '_' ) ) { continue ; }
if ( has _network _sub _keys ( subnetTree [ mapKey ] ) ) {
2023-05-18 00:36:32 +00:00
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 ) {
2023-05-23 07:38:41 +00:00
if ( mapKey . startsWith ( '_' ) ) { continue ; }
if ( has _network _sub _keys ( subnetTree [ mapKey ] ) ) {
2023-05-20 21:15:55 +00:00
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 ) {
2023-05-23 07:38:41 +00:00
if ( mapKey . startsWith ( '_' ) ) { continue ; }
if ( has _network _sub _keys ( subnetTree [ mapKey ] ) ) {
2023-05-18 00:36:32 +00:00
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
}
2023-05-23 07:38:41 +00:00
function get _consolidated _property ( subnetTree , property ) {
let allValues = get _property _values ( subnetTree , property )
// https://stackoverflow.com/questions/14832603/check-if-all-values-of-array-are-equal
let allValuesMatch = allValues . every ( ( val , i , arr ) => val === arr [ 0 ] )
if ( allValuesMatch ) {
return allValues [ 0 ]
} else {
return ''
}
}
function get _property _values ( subnetTree , property ) {
let propValues = [ ]
for ( let mapKey in subnetTree ) {
if ( has _network _sub _keys ( subnetTree [ mapKey ] ) ) {
propValues . push . apply ( propValues , get _property _values ( subnetTree [ mapKey ] , property ) )
} else {
// The "else" above is a bit different because it will start tracking values for subnets which are
// in the hierarchy, but not displayed. Those are always blank so it messes up the value list
propValues . push ( subnetTree [ mapKey ] [ property ] || '' )
}
}
return propValues
}
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 ;
}
2023-05-23 07:38:41 +00:00
function mutate _subnet _map ( verb , network , subnetTree , propValue = '' ) {
if ( subnetTree === '' ) { subnetTree = subnetMap }
2023-05-18 01:34:53 +00:00
for ( let mapKey in subnetTree ) {
2023-05-23 07:38:41 +00:00
if ( mapKey . startsWith ( '_' ) ) { continue ; }
if ( has _network _sub _keys ( subnetTree [ mapKey ] ) ) {
mutate _subnet _map ( verb , network , subnetTree [ mapKey ] , propValue )
2023-05-18 01:34:53 +00:00
}
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' ) {
2024-06-24 15:52:29 +00:00
if ( netSize < minSubnetSizes [ operatingMode ] ) {
2023-05-20 21:15:55 +00:00
let new _networks = split _network ( netSplit [ 0 ] , netSize )
2023-05-23 07:38:41 +00:00
// Could maybe optimize this for readability with some null coalescing
2023-05-19 06:28:43 +00:00
subnetTree [ mapKey ] [ new _networks [ 0 ] ] = { }
subnetTree [ mapKey ] [ new _networks [ 1 ] ] = { }
2023-05-23 07:38:41 +00:00
// Options:
// [ Selected ] Copy note to both children and delete parent note
// [ Possible ] Blank out the new and old subnet notes
if ( subnetTree [ mapKey ] . hasOwnProperty ( '_note' ) ) {
subnetTree [ mapKey ] [ new _networks [ 0 ] ] [ '_note' ] = subnetTree [ mapKey ] [ '_note' ]
subnetTree [ mapKey ] [ new _networks [ 1 ] ] [ '_note' ] = subnetTree [ mapKey ] [ '_note' ]
}
delete subnetTree [ mapKey ] [ '_note' ]
if ( subnetTree [ mapKey ] . hasOwnProperty ( '_color' ) ) {
subnetTree [ mapKey ] [ new _networks [ 0 ] ] [ '_color' ] = subnetTree [ mapKey ] [ '_color' ]
subnetTree [ mapKey ] [ new _networks [ 1 ] ] [ '_color' ] = subnetTree [ mapKey ] [ '_color' ]
2023-05-20 21:15:55 +00:00
}
2023-05-23 07:38:41 +00:00
delete subnetTree [ mapKey ] [ '_color' ]
2024-06-24 15:52:29 +00:00
} else {
switch ( operatingMode ) {
case 'AWS' :
2024-06-24 17:55:02 +00:00
var modal _error _message = 'The minimum IPv4 subnet size for AWS is /' + minSubnetSizes [ operatingMode ] + '.<br/><br/>More Information:<br/><a href="https://docs.aws.amazon.com/vpc/latest/userguide/subnet-sizing.html#subnet-sizing-ipv4" target="_blank">Amazon Virtual Private Cloud > User Guide > Subnet CIDR Blocks > Subnet Sizing for IPv4</a>'
break ;
2024-06-24 15:52:29 +00:00
case 'AZURE' :
2024-06-24 17:55:02 +00:00
var modal _error _message = 'The minimum IPv4 subnet size for Azure is /' + minSubnetSizes [ operatingMode ] + '.<br/><br/>More Information:<br/><a href="https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#how-small-and-how-large-can-virtual-networks-and-subnets-be" target="_blank">Azure Virtual Network FAQ > How small and how large can virtual networks and subnets be?</a>'
2024-06-24 15:52:29 +00:00
break ;
default :
2024-06-24 17:55:02 +00:00
var modal _error _message = 'The minimum size for an IPv4 subnet is /' + minSubnetSizes [ operatingMode ] + '.<br/><br/>More Information:<br/><a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing" target="_blank">Wikipedia - Classless Inter-Domain Routing</a>'
2024-06-24 15:52:29 +00:00
break ;
2024-06-24 17:55:02 +00:00
}
show _warning _modal ( '<div>' + modal _error _message + '</div>' )
2023-05-20 21:15:55 +00:00
}
2023-05-23 07:38:41 +00:00
} else if ( verb === 'join' ) {
// Options:
// [ Selected ] Keep note if all the notes are the same, blank them out if they differ. Most intuitive
// [ Possible ] Lose note data for all deleted subnets.
// [ Possible ] Keep note from first subnet in the join scope. Reasonable but I think rarely will the note be kept by the user
// [ Possible ] Concatenate all notes. Ugly and won't really be useful for more than two subnets being joined
subnetTree [ mapKey ] = {
'_note' : get _consolidated _property ( subnetTree [ mapKey ] , '_note' ) ,
'_color' : get _consolidated _property ( subnetTree [ mapKey ] , '_color' )
2023-05-20 21:15:55 +00:00
}
2023-05-23 07:38:41 +00:00
} else if ( verb === 'note' ) {
subnetTree [ mapKey ] [ '_note' ] = propValue
} else if ( verb === 'color' ) {
subnetTree [ mapKey ] [ '_color' ] = propValue
2023-05-18 01:34:53 +00:00
} else {
// How did you get here?
}
}
}
2023-05-18 04:29:21 +00:00
}
2023-05-19 06:08:38 +00:00
2024-06-24 15:52:29 +00:00
function switchMode ( operatingMode ) {
let isSwitched = true ;
if ( subnetMap !== null ) {
if ( validateSubnetSizes ( subnetMap , minSubnetSizes [ operatingMode ] ) ) {
2024-06-24 22:04:36 +00:00
2024-06-24 15:52:29 +00:00
renderTable ( operatingMode ) ;
2024-06-24 17:07:26 +00:00
set _usable _ips _title ( operatingMode ) ;
2024-06-24 15:52:29 +00:00
2024-06-24 22:04:36 +00:00
$ ( '#netsize' ) . attr ( 'pattern' , netsizePatterns [ operatingMode ] ) ;
2024-06-24 15:52:29 +00:00
$ ( '#input_form' ) . removeClass ( 'was-validated' ) ;
2024-06-24 22:04:36 +00:00
$ ( '#input_form' ) . rules ( 'remove' , 'netsize' ) ;
2024-06-24 15:52:29 +00:00
switch ( operatingMode ) {
case 'AWS' :
2024-06-24 22:04:36 +00:00
var validate _error _message = 'AWS Mode - Smallest size is /' + minSubnetSizes [ operatingMode ]
2024-06-24 17:55:02 +00:00
break ;
2024-06-24 15:52:29 +00:00
case 'AZURE' :
2024-06-24 22:04:36 +00:00
var validate _error _message = 'Azure Mode - Smallest size is /' + minSubnetSizes [ operatingMode ]
2024-06-24 15:52:29 +00:00
break ;
default :
2024-06-24 22:04:36 +00:00
var validate _error _message = 'Smallest size is /' + minSubnetSizes [ operatingMode ]
2024-06-24 15:52:29 +00:00
break ;
}
// Modify jquery validation rule
2024-06-24 22:04:36 +00:00
$ ( '#input_form #netsize' ) . rules ( 'add' , {
2024-06-24 15:52:29 +00:00
required : true ,
pattern : netsizePatterns [ operatingMode ] ,
messages : {
2024-06-24 22:04:36 +00:00
required : 'Please enter a network size' ,
2024-06-24 17:55:02 +00:00
pattern : validate _error _message
2024-06-24 15:52:29 +00:00
}
} ) ;
// Remove active class from all buttons if needed
2024-06-24 17:07:26 +00:00
$ ( '#dropdown_standard, #dropdown_azure, #dropdown_aws' ) . removeClass ( 'active' ) ;
$ ( '#dropdown_' + operatingMode . toLowerCase ( ) ) . addClass ( 'active' ) ;
2024-06-24 15:52:29 +00:00
isSwitched = true ;
} else {
2024-06-24 17:55:02 +00:00
switch ( operatingMode ) {
case 'AWS' :
var modal _error _message = 'One or more subnets are smaller than the minimum allowed for AWS.<br/>The smallest size allowed is /' + minSubnetSizes [ operatingMode ] + '.<br/>See: <a href="https://docs.aws.amazon.com/vpc/latest/userguide/subnet-sizing.html#subnet-sizing-ipv4" target="_blank">Amazon Virtual Private Cloud > User Guide > Subnet CIDR Blocks > Subnet Sizing for IPv4</a>'
break ;
case 'AZURE' :
var modal _error _message = 'One or more subnets are smaller than the minimum allowed for Azure.<br/>The smallest size allowed is /' + minSubnetSizes [ operatingMode ] + '.<br/>See: <a href="https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#how-small-and-how-large-can-virtual-networks-and-subnets-be" target="_blank">Azure Virtual Network FAQ > How small and how large can virtual networks and subnets be?</a>'
break ;
default :
2024-06-24 22:04:36 +00:00
var validate _error _message = 'Unknown Error'
2024-06-24 17:55:02 +00:00
break ;
}
show _warning _modal ( '<div>' + modal _error _message + '</div>' ) ;
2024-06-24 15:52:29 +00:00
isSwitched = false ;
}
} else {
//unlikely to get here.
reset ( ) ;
}
return isSwitched ;
}
function validateSubnetSizes ( subnetMap , minSubnetSize ) {
let isValid = true ;
const validate = ( subnetTree ) => {
for ( let key in subnetTree ) {
if ( key . startsWith ( '_' ) ) continue ; // Skip special keys
let [ _ , size ] = key . split ( '/' ) ;
if ( parseInt ( size ) > minSubnetSize ) {
isValid = false ;
return ; // Early exit if any subnet is invalid
}
if ( typeof subnetTree [ key ] === 'object' ) {
validate ( subnetTree [ key ] ) ; // Recursively validate subnets
}
}
} ;
validate ( subnetMap ) ;
return isValid ;
}
2024-06-24 17:07:26 +00:00
function set _usable _ips _title ( operatingMode ) {
2024-06-24 15:52:29 +00:00
switch ( operatingMode ) {
case 'AWS' :
2024-06-24 21:53:48 +00:00
$ ( '#useableHeader' ) . html ( 'Usable IPs (<a href="https://docs.aws.amazon.com/vpc/latest/userguide/subnet-sizing.html#subnet-sizing-ipv4" target="_blank" style="color:#000; border-bottom: 1px dotted #000; text-decoration: dotted" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="AWS reserves 5 addresses in each subnet for platform use.<br/>Click to navigate to the AWS documentation.">AWS</a>)' )
2024-06-24 17:07:26 +00:00
break ;
2024-06-24 15:52:29 +00:00
case 'AZURE' :
2024-06-24 21:53:48 +00:00
$ ( '#useableHeader' ) . html ( 'Usable IPs (<a href="https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets" target="_blank" style="color:#000; border-bottom: 1px dotted #000; text-decoration: dotted" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Azure reserves 5 addresses in each subnet for platform use.<br/>Click to navigate to the Azure documentation.">Azure</a>)' )
2024-06-24 15:52:29 +00:00
break ;
default :
2024-06-24 17:07:26 +00:00
$ ( '#useableHeader' ) . html ( 'Usable IPs' )
2024-06-24 15:52:29 +00:00
break ;
}
2024-06-24 21:53:48 +00:00
$ ( '[data-bs-toggle="tooltip"]' ) . tooltip ( )
2024-06-24 15:52:29 +00:00
}
2023-05-19 06:08:38 +00:00
function show _warning _modal ( message ) {
2024-06-24 22:04:36 +00:00
var notifyModal = new bootstrap . Modal ( document . getElementById ( 'notifyModal' ) , { } ) ;
2023-05-19 06:08:38 +00:00
$ ( '#notifyModal .modal-body' ) . html ( message )
notifyModal . show ( )
}
2023-05-18 04:29:21 +00:00
$ ( document ) . ready ( function ( ) {
2024-06-24 15:52:29 +00:00
// Initialize the jQuery Validation on the form
var validator = $ ( '#input_form' ) . validate ( {
2024-06-24 21:53:48 +00:00
onfocusout : function ( element ) {
$ ( element ) . valid ( ) ;
} ,
2024-06-24 15:52:29 +00:00
rules : {
network : {
required : true ,
2024-06-24 22:04:36 +00:00
pattern : '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
2024-06-24 15:52:29 +00:00
} ,
netsize : {
required : true ,
2024-06-24 22:04:36 +00:00
pattern : '^([0-9]|[12][0-9]|3[0-2])$'
2024-06-24 15:52:29 +00:00
}
} ,
messages : {
network : {
2024-06-24 22:04:36 +00:00
required : 'Please enter a network' ,
pattern : 'Must be a valid IPv4 Address'
2024-06-24 15:52:29 +00:00
} ,
netsize : {
2024-06-24 22:04:36 +00:00
required : 'Please enter a network size' ,
pattern : 'Smallest size is /32'
2024-06-24 15:52:29 +00:00
}
} ,
errorPlacement : function ( error , element ) {
2024-10-13 22:19:09 +00:00
//console.log(error);
//console.log(element);
2024-06-24 21:53:48 +00:00
if ( error [ 0 ] . innerHTML !== '' ) {
2024-10-13 22:19:09 +00:00
//console.log('Error Placement - Text')
2024-06-24 21:53:48 +00:00
if ( ! element . data ( 'errorIsVisible' ) ) {
bootstrap . Tooltip . getInstance ( element ) . setContent ( { '.tooltip-inner' : error [ 0 ] . innerHTML } )
element . tooltip ( 'show' ) ;
element . data ( 'errorIsVisible' , true )
}
} else {
2024-10-13 22:19:09 +00:00
//console.log('Error Placement - Empty')
//console.log(element);
2024-06-24 21:53:48 +00:00
if ( element . data ( 'errorIsVisible' ) ) {
element . tooltip ( 'hide' ) ;
element . data ( 'errorIsVisible' , false )
}
}
2024-10-13 22:19:09 +00:00
//console.log(element);
2024-06-24 15:52:29 +00:00
} ,
2024-06-24 21:53:48 +00:00
// This success function appears to be required as errorPlacement() does not fire without the success function
// being defined.
success : function ( label , element ) { } ,
2024-06-24 15:52:29 +00:00
// When the form is valid, add the 'was-validated' class
submitHandler : function ( form ) {
form . classList . add ( 'was-validated' ) ;
form . submit ( ) ; // Submit the form
}
} ) ;
2023-05-25 06:35:45 +00:00
let autoConfigResult = processConfigUrl ( ) ;
2024-06-24 22:04:36 +00:00
if ( ! autoConfigResult ) {
2023-05-25 06:35:45 +00:00
reset ( ) ;
}
2023-05-19 06:08:38 +00:00
} ) ;
2023-05-21 06:05:19 +00:00
2024-10-08 07:24:38 +00:00
function exportConfig ( isMinified = true ) {
const baseNetwork = Object . keys ( subnetMap ) [ 0 ]
let miniSubnetMap = { } ;
if ( isMinified ) {
minifySubnetMap ( miniSubnetMap , subnetMap , baseNetwork )
}
2024-06-24 17:55:02 +00:00
if ( operatingMode !== 'Standard' ) {
return {
'config_version' : configVersion ,
'operating_mode' : operatingMode ,
2024-10-08 07:24:38 +00:00
'base_network' : baseNetwork ,
'subnets' : isMinified ? miniSubnetMap : subnetMap ,
2024-06-24 17:55:02 +00:00
}
} else {
return {
'config_version' : configVersion ,
2024-10-08 07:24:38 +00:00
'base_network' : baseNetwork ,
'subnets' : isMinified ? miniSubnetMap : subnetMap ,
2024-06-24 17:55:02 +00:00
}
2023-05-21 06:05:19 +00:00
}
}
2023-05-25 06:35:45 +00:00
function getConfigUrl ( ) {
2024-10-08 07:24:38 +00:00
// Deep Copy
let defaultExport = JSON . parse ( JSON . stringify ( exportConfig ( true ) ) ) ;
2023-05-25 06:35:45 +00:00
renameKey ( defaultExport , 'config_version' , 'v' )
2024-10-08 07:24:38 +00:00
renameKey ( defaultExport , 'base_network' , 'b' )
2024-06-24 17:55:02 +00:00
if ( defaultExport . hasOwnProperty ( 'operating_mode' ) ) {
renameKey ( defaultExport , 'operating_mode' , 'm' )
}
2023-05-25 06:35:45 +00:00
renameKey ( defaultExport , 'subnets' , 's' )
2024-10-13 22:59:42 +00:00
//console.log(JSON.stringify(defaultExport))
2023-05-25 18:11:03 +00:00
return '/index.html?c=' + urlVersion + LZString . compressToEncodedURIComponent ( JSON . stringify ( defaultExport ) )
2023-05-25 06:35:45 +00:00
}
function processConfigUrl ( ) {
const params = new Proxy ( new URLSearchParams ( window . location . search ) , {
get : ( searchParams , prop ) => searchParams . get ( prop ) ,
} ) ;
if ( params [ 'c' ] !== null ) {
2023-05-25 18:11:03 +00:00
// First character is the version of the URL string, in case the mechanism of encoding changes
let urlVersion = params [ 'c' ] . substring ( 0 , 1 )
let urlData = params [ 'c' ] . substring ( 1 )
2024-06-24 15:52:29 +00:00
let urlConfig = JSON . parse ( LZString . decompressFromEncodedURIComponent ( params [ 'c' ] . substring ( 1 ) ) )
renameKey ( urlConfig , 'v' , 'config_version' )
2024-06-24 17:55:02 +00:00
if ( urlConfig . hasOwnProperty ( 'm' ) ) {
renameKey ( urlConfig , 'm' , 'operating_mode' )
}
2024-06-24 15:52:29 +00:00
renameKey ( urlConfig , 's' , 'subnets' )
2024-10-08 07:24:38 +00:00
if ( urlConfig [ 'config_version' ] === '1' ) {
// Version 1 Configs used full subnet strings as keys and just shortned the _note->_n and _color->_c keys
expandKeys ( urlConfig [ 'subnets' ] )
} else if ( urlConfig [ 'config_version' ] === '2' ) {
// Version 2 Configs uses the Nth Position representation for subnet keys and requires the base_network
// option. It also uses n/c for note/color
if ( urlConfig . hasOwnProperty ( 'b' ) ) {
renameKey ( urlConfig , 'b' , 'base_network' )
}
let expandedSubnetMap = { } ;
expandSubnetMap ( expandedSubnetMap , urlConfig [ 'subnets' ] , urlConfig [ 'base_network' ] )
urlConfig [ 'subnets' ] = expandedSubnetMap
}
2024-06-24 15:52:29 +00:00
importConfig ( urlConfig )
return true
2023-05-25 06:35:45 +00:00
}
}
2024-10-08 07:24:38 +00:00
function minifySubnetMap ( minifiedMap , referenceMap , baseNetwork ) {
for ( let subnet in referenceMap ) {
if ( subnet . startsWith ( '_' ) ) continue ;
const nthRepresentation = getNthSubnet ( baseNetwork , subnet ) ;
minifiedMap [ nthRepresentation ] = { }
if ( referenceMap [ subnet ] . hasOwnProperty ( '_note' ) ) {
minifiedMap [ nthRepresentation ] [ 'n' ] = referenceMap [ subnet ] [ '_note' ]
}
if ( referenceMap [ subnet ] . hasOwnProperty ( '_color' ) ) {
minifiedMap [ nthRepresentation ] [ 'c' ] = referenceMap [ subnet ] [ '_color' ]
}
if ( Object . keys ( referenceMap [ subnet ] ) . some ( key => ! key . startsWith ( '_' ) ) ) {
minifySubnetMap ( minifiedMap [ nthRepresentation ] , referenceMap [ subnet ] , baseNetwork ) ;
}
}
}
function expandSubnetMap ( expandedMap , miniMap , baseNetwork ) {
for ( let mapKey in miniMap ) {
if ( mapKey === 'n' || mapKey === 'c' ) {
2023-05-25 06:35:45 +00:00
continue ;
}
2024-10-08 07:24:38 +00:00
let subnetKey = getSubnetFromNth ( baseNetwork , mapKey )
expandedMap [ subnetKey ] = { }
if ( has _network _sub _keys ( miniMap [ mapKey ] ) ) {
expandSubnetMap ( expandedMap [ subnetKey ] , miniMap [ mapKey ] , baseNetwork )
2023-05-25 06:35:45 +00:00
} else {
2024-10-08 07:24:38 +00:00
if ( miniMap [ mapKey ] . hasOwnProperty ( 'n' ) ) {
expandedMap [ subnetKey ] [ '_note' ] = miniMap [ mapKey ] [ 'n' ]
2023-05-25 06:35:45 +00:00
}
2024-10-08 07:24:38 +00:00
if ( miniMap [ mapKey ] . hasOwnProperty ( 'c' ) ) {
expandedMap [ subnetKey ] [ '_color' ] = miniMap [ mapKey ] [ 'c' ]
2023-05-25 06:35:45 +00:00
}
}
}
}
2024-10-08 07:24:38 +00:00
// For Config Version 1 Backwards Compatibility
2023-05-25 06:35:45 +00:00
function expandKeys ( subnetTree ) {
for ( let mapKey in subnetTree ) {
if ( mapKey . startsWith ( '_' ) ) {
continue ;
}
if ( has _network _sub _keys ( subnetTree [ mapKey ] ) ) {
expandKeys ( subnetTree [ mapKey ] )
} else {
if ( subnetTree [ mapKey ] . hasOwnProperty ( '_n' ) ) {
renameKey ( subnetTree [ mapKey ] , '_n' , '_note' )
}
if ( subnetTree [ mapKey ] . hasOwnProperty ( '_c' ) ) {
renameKey ( subnetTree [ mapKey ] , '_c' , '_color' )
}
}
}
}
function renameKey ( obj , oldKey , newKey ) {
if ( oldKey !== newKey ) {
Object . defineProperty ( obj , newKey ,
Object . getOwnPropertyDescriptor ( obj , oldKey ) ) ;
delete obj [ oldKey ] ;
}
}
2023-05-21 06:05:19 +00:00
function importConfig ( text ) {
2024-06-24 17:55:02 +00:00
if ( text [ 'config_version' ] === '1' ) {
2024-10-08 07:24:38 +00:00
var [ subnetNet , subnetSize ] = Object . keys ( text [ 'subnets' ] ) [ 0 ] . split ( '/' )
} else if ( text [ 'config_version' ] === '2' ) {
var [ subnetNet , subnetSize ] = text [ 'base_network' ] . split ( '/' )
2023-05-21 06:05:19 +00:00
}
2024-10-08 07:24:38 +00:00
$ ( '#network' ) . val ( subnetNet )
$ ( '#netsize' ) . val ( subnetSize )
subnetMap = text [ 'subnets' ] ;
operatingMode = text [ 'operating_mode' ] || 'Standard'
switchMode ( operatingMode ) ;
2023-05-23 07:38:41 +00:00
}
2024-06-24 22:04:36 +00:00
const rgba2hex = ( rgba ) => ` # ${ rgba . match ( /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/ ) . slice ( 1 ) . map ( ( n , i ) => ( i === 3 ? Math . round ( parseFloat ( n ) * 255 ) : parseFloat ( n ) ) . toString ( 16 ) . padStart ( 2 , '0' ) . replace ( 'NaN' , '' ) ) . join ( '' ) } `