Merge branch 'release/v0.3'
This commit is contained in:
commit
a68cc82dd4
11 changed files with 824 additions and 1 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.idea/*
|
||||
**/node_modules/*
|
40
README.md
40
README.md
|
@ -1,6 +1,44 @@
|
|||
# Visual Subnet Calculator
|
||||
|
||||
To be completed.
|
||||
Visual Subnet Calculator is a modernized tool based on the original work by [davidc](https://github.com/davidc/subnets).
|
||||
It strives to be a tool for quickly designing networks and collaborating on that design with others. It focuses on
|
||||
expediting the work of network administrators, not academic subnetting math.
|
||||
|
||||
## Design Tenants
|
||||
|
||||
The following tenants are the most important values that drive the design of the tool. New features, pull requests, etc
|
||||
should align to these tenants, or propose an adjustment to the tenants.
|
||||
|
||||
- **Simplicity is king.** Network admins are busy and Visual Subnet Calculator should always be easy for FIRST TIME USERS to
|
||||
quickly and intuitively use.
|
||||
- **Subnetting is design work.** Promote features that enhance visual clarity and easy mental processing of even the most
|
||||
complex architectures.
|
||||
- **Users control the data.** We store nothing, but provide convenient ways for users to save and share their designs.
|
||||
- **Embrace community contributions.** Consider and respond to all feedback and pull requests in the context of these
|
||||
tenants.
|
||||
|
||||
## Building From Source
|
||||
|
||||
If you have a more opinionated best-practice way to lay out this repository please open an issue.
|
||||
|
||||
Build prerequisites:
|
||||
- npm
|
||||
- sass
|
||||
|
||||
Compile from source:
|
||||
|
||||
```shell
|
||||
# Clone the repository
|
||||
> git clone https://github.com/ckabalan/visualsubnetcalc
|
||||
# Change to the sources directory
|
||||
> cd visualsubnetcalc/src
|
||||
# Install Bootstrap
|
||||
> npm install
|
||||
# Compile Bootstrap
|
||||
> sass --style compressed scss/custom.scss:../dist/bootstrap.min.css
|
||||
```
|
||||
|
||||
The full application should then be available within `./dist/`, open `./dist/index.html` in a browser.
|
||||
|
||||
## License
|
||||
|
||||
|
|
6
dist/bootstrap.min.css
vendored
Normal file
6
dist/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/bootstrap.min.css.map
vendored
Normal file
1
dist/bootstrap.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
127
dist/index.html
vendored
Normal file
127
dist/index.html
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Visual Subnet Calculator</title>
|
||||
<link rel="stylesheet" href="bootstrap.min.css">
|
||||
<link href="main.css" rel="stylesheet">
|
||||
<meta name="description" content="Quickly design and collaborate on network design. Visual Subnet Calculator focuses on expediting the work of network administrators, not academic subnetting math.">
|
||||
<meta name="robots" content="index, follow" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-xxl mt-3">
|
||||
<div class="float-end" id="navigation">
|
||||
<a href="#" id="info_icon" data-bs-toggle="modal" data-bs-target="#aboutModal">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
||||
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
|
||||
</svg>
|
||||
</a><a href="https://github.com/ckabalan/visualsubnetcalc" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<h1>Visual Subnet Calculator <span style="font-size:1rem;">(UNDER CONSTRUCTION)</span></h1>
|
||||
<div class="alert alert-primary alert-dismissible show mt-3" role="alert">
|
||||
Enter the network you wish to subnet and use the Split/Join buttons on the right to start designing!
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="row font-monospace g-2 mb-3">
|
||||
<div class="col-lg-2 col-md-3 col-4">
|
||||
<div><label for="basic-url" class="form-label mb-0 ms-1">Network Address</label></div>
|
||||
<div><input id="network" type="text" class="form-control" value="10.0.0.0" aria-label="Network Address"></div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div style="height:2rem"></div>
|
||||
<div>/</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-4">
|
||||
<div><label for="basic-url" class="form-label mb-0 ms-1">Network Size</label></div>
|
||||
<div><input id="netsize" type="text" class="form-control w-10" value="16" aria-label="Network Size"></div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-3">
|
||||
<div style="height:1.5rem"></div>
|
||||
<div>
|
||||
<button id="btn_go" class="btn btn-success mb-0 mt-auto" type="button">Go</button>
|
||||
<button id="btn_reset" class="btn btn-danger mb-0 mt-auto" type="button">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<table id="calc" class="table table-bordered font-monospace">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="subnetHeader" style="display: table-cell;">Subnet address</th>
|
||||
<th id="netmaskHeader" style="display: none;">Netmask</th>
|
||||
<th id="rangeHeader" style="display: table-cell;">Range of addresses</th>
|
||||
<th id="useableHeader" style="display: table-cell;">Useable IPs</th>
|
||||
<th id="hostsHeader" style="display: table-cell;">Hosts</th>
|
||||
<th id="noteHeader" colspan="100%" style="display: table-cell;">
|
||||
Note
|
||||
<div style="display:inline-block; float:right;"><span class="split">Split</span>/<span class="join">Join</span></div>
|
||||
</th>
|
||||
<!--
|
||||
<th id="joinHeader" colspan="100%" style="display: table-cell;"><span class="split">Split</span>/<span class="join">Join</span></th>
|
||||
-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="calcbody">
|
||||
<tr id="row_10-0-0-0_17">
|
||||
<td class="row_address">Loading...</td>
|
||||
<td class="row_range"></td>
|
||||
<td class="row_usable"></td>
|
||||
<td class="row_hosts"></td>
|
||||
<td class="note"><label><input type="text" class="form-control shadow-none p-0"></label></td>
|
||||
<td rowspan="1" colspan="13" class="split rotate"><span></span></td>
|
||||
<td rowspan="14" colspan="1" class="join rotate"><span></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="modal fade" id="aboutModal" tabindex="-1" aria-labelledby="aboutModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="aboutModalLabel">About Visual Subnet Calculator</h3>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Visual Subnet Calculator strives to be a tool for quickly designing networks and collaborating on that design with others. It focuses on expediting the work of network administrators, not academic subnetting math.<br />
|
||||
<br/>
|
||||
<h4>Design Tenants</h4>
|
||||
<ul>
|
||||
<li><span style="font-weight:bold">Simplicity is king.</span> Network admins are busy and Visual Subnet Calculator should always be easy for FIRST TIME USERS to quickly and intuitively use.</li>
|
||||
<li><span style="font-weight:bold">Subnetting is design work.</span> Promote features that enhance visual clarity and easy mental processing of even the mostcomplex architectures.</li>
|
||||
<li><span style="font-weight:bold">Users control the data.</span> We store nothing, but provide convenient ways for users to save and share their designs.</li>
|
||||
<li><span style="font-weight:bold">Embrace community contributions.</span> Consider and respond to all feedback and pull requests in the context of these tenants.</li>
|
||||
</ul>
|
||||
<h4 class="mt-4">Credits</h4>
|
||||
Developed by <a href="https://www.caesarkabalan.com/" target="_blank">Caesar Kabalan</a> with help from <a href="https://github.com/ckabalan/visualsubnetcalc/graphs/contributors" target="_blank">GitHub Contributors</a>!<br/>
|
||||
<br/>
|
||||
Special thanks to <a href="https://www.davidc.net/" target="_blank">davidc</a> for the <a href="https://www.davidc.net/sites/default/subnets/subnets.html" target="_blank">original tool</a> this website is inspired by.<br/>
|
||||
<br/>
|
||||
All contributions, even suggestions or bug reports, are welcome at:<br/>
|
||||
<ul>
|
||||
<li>GitHub: <a href="https://github.com/ckabalan/visualsubnetcalc" target="_blank">https://github.com/ckabalan/visualsubnetcalc</a></li>
|
||||
<li>LinkedIn: <a href="https://www.linkedin.com/in/caesarkabalan/" target="_blank">Caesar Kabalan</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.0.min.js"
|
||||
integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="main.js"></script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
138
dist/main.css
vendored
Normal file
138
dist/main.css
vendored
Normal file
|
@ -0,0 +1,138 @@
|
|||
:root {
|
||||
--split-foreground: #000000;
|
||||
--split-background: indianred;
|
||||
--join-foreground: #000000;
|
||||
--join-background: skyblue;
|
||||
}
|
||||
|
||||
.container-xxl {
|
||||
min-width: 576px;
|
||||
}
|
||||
|
||||
#navigation a {
|
||||
color:#000000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#subnet_input #network {
|
||||
flex-grow: 0;
|
||||
flex-basis: 11rem;
|
||||
}
|
||||
#subnet_input #netsize {
|
||||
flex-grow: 0;
|
||||
flex-basis: 4rem;
|
||||
}
|
||||
|
||||
#calc {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#calc>tbody>tr>td, #calc>tbody>tr>th, #calc>tfoot>tr>td, #calc>tfoot>tr>th, #calc>thead>tr>td, #calc>thead>tr>th {
|
||||
/* Equivalent to p-1 */
|
||||
padding: 0.25rem;
|
||||
/* Equivalent to p-2 */
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/*
|
||||
#joinHeader {
|
||||
border:none;
|
||||
}
|
||||
|
||||
#calc thead {
|
||||
border-right-width: 1px;
|
||||
border-bottom-width: 0px;
|
||||
}*/
|
||||
|
||||
#calc span.split {
|
||||
color: var(--split-background);
|
||||
}
|
||||
|
||||
#calc span.join {
|
||||
color: var(--join-background);
|
||||
}
|
||||
|
||||
#calc td.split {
|
||||
background-color: var(--split-background);
|
||||
color: var(--split-foreground);
|
||||
cursor: pointer;
|
||||
min-width: 2.3rem;
|
||||
width: 1%;
|
||||
font-size:1rem;
|
||||
}
|
||||
|
||||
#calc td.join {
|
||||
background-color: var(--join-background);
|
||||
color: var(--join-foreground);
|
||||
cursor: pointer;
|
||||
min-width: 2.3rem;
|
||||
width: 1%;
|
||||
font-size:1rem;
|
||||
}
|
||||
|
||||
#calc td.split, #calc td.join {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#calc td.split span, #calc td.join span {
|
||||
padding-right: 0.4rem;
|
||||
}
|
||||
#calc .note {
|
||||
padding-left:0.5rem;
|
||||
padding-right:0.5rem;
|
||||
}
|
||||
|
||||
#calc .note label,input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#calc .row_address {
|
||||
white-space: nowrap;
|
||||
padding-left:0.5rem;
|
||||
padding-right:0.5rem;
|
||||
}
|
||||
|
||||
#calc .row_range {
|
||||
/* TODO: Make this a checkbox?
|
||||
white-space: nowrap;
|
||||
*/
|
||||
padding-left:0.5rem;
|
||||
padding-right:0.5rem;
|
||||
}
|
||||
|
||||
#calc .row_usable {
|
||||
/* TODO: Make this a checkbox?
|
||||
white-space: nowrap;
|
||||
*/
|
||||
padding-left:0.5rem;
|
||||
padding-right:0.5rem;
|
||||
}
|
||||
|
||||
#calc .row_hosts {
|
||||
width:1%;
|
||||
white-space: nowrap;
|
||||
padding-left:0.5rem;
|
||||
padding-right:0.5rem;
|
||||
}
|
||||
#calc .note {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
#calc .note input {
|
||||
border: none !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/a/47245068/606974 */
|
||||
.rotate {
|
||||
vertical-align: middle;
|
||||
text-align: end;
|
||||
}
|
||||
.rotate span {
|
||||
-ms-writing-mode: tb-rl;
|
||||
-webkit-writing-mode: vertical-rl;
|
||||
writing-mode: vertical-rl;
|
||||
white-space: nowrap;
|
||||
padding-top: 0.25rem;
|
||||
}
|
230
dist/main.js
vendored
Normal file
230
dist/main.js
vendored
Normal file
|
@ -0,0 +1,230 @@
|
|||
let subnetMap = {};
|
||||
let maxNetSize = 0;
|
||||
let infoColumnCount = 5
|
||||
$('#btn_go').on('click', function() {
|
||||
reset();
|
||||
})
|
||||
|
||||
$('#btn_reset').on('click', function() {
|
||||
reset();
|
||||
})
|
||||
|
||||
function reset() {
|
||||
let rootCidr = get_network($('#network').val(), $('#netsize').val()) + '/' + $('#netsize').val()
|
||||
subnetMap = {}
|
||||
subnetMap[rootCidr] = {}
|
||||
maxNetSize = parseInt($('#netsize').val())
|
||||
/*
|
||||
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': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
renderTable();
|
||||
}
|
||||
|
||||
$('#calcbody').on('click', 'td.split,td.join', function(event) {
|
||||
// HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags
|
||||
console.log(this.dataset.subnet)
|
||||
mutate_subnet_map(this.dataset.mutateVerb, this.dataset.subnet, subnetMap)
|
||||
renderTable();
|
||||
})
|
||||
|
||||
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('/')
|
||||
addRow(subnet_split[0], parseInt(subnet_split[1]), (infoColumnCount + maxDepth - depth))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addRow(network, netSize, colspan) {
|
||||
// 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' +
|
||||
' <td class="note"><label><input type="text" class="form-control shadow-none p-0"></label></td>\n' +
|
||||
' <td rowspan="1" colspan="' + colspan + '" class="split rotate" data-subnet="' + network + '/' + netSize + '" data-mutate-verb="split"><span>/' + netSize + '</span></td>\n'
|
||||
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
|
||||
// of ancestors so you can set colspan.
|
||||
// DONE: If the subnet address (without the mask) matches a larger subnet address
|
||||
// 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.
|
||||
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, [])
|
||||
newRow += ' <td rowspan="' + networkChildrenCount + '" colspan="1" class="join rotate" data-subnet="' + matchingNetwork + '" data-mutate-verb="join"><span>/' + matchingNetwork.split('/')[1] + '</span></td>\n'
|
||||
}
|
||||
}
|
||||
newRow += ' </tr>';
|
||||
|
||||
$('#calcbody').append(newRow)
|
||||
console.log(network)
|
||||
console.log(netSize)
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
if (verb === 'split') {
|
||||
let netSplit = mapKey.split('/')
|
||||
let new_networks = split_network(netSplit[0], parseInt(netSplit[1]))
|
||||
subnetTree[mapKey][new_networks[0]] = {}
|
||||
subnetTree[mapKey][new_networks[1]] = {}
|
||||
} else if (verb === 'join') {
|
||||
subnetTree[mapKey] = {}
|
||||
} else {
|
||||
// How did you get here?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
reset();
|
||||
});
|
215
src/cloudformation.yaml
Normal file
215
src/cloudformation.yaml
Normal file
|
@ -0,0 +1,215 @@
|
|||
---
|
||||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Description: Visual Subnet Calculator
|
||||
Metadata:
|
||||
AWS::CloudFormation::Interface:
|
||||
ParameterGroups:
|
||||
- Label:
|
||||
default: Naming
|
||||
Parameters:
|
||||
- DomainName
|
||||
ParameterLabels:
|
||||
DomainName:
|
||||
default: Domain Name
|
||||
Parameters:
|
||||
DomainName:
|
||||
Type: String
|
||||
Default: ''
|
||||
Description: Domain Name for Route53 Hosted Zone and Static S3 Site
|
||||
Resources:
|
||||
OriginAccessIdentity:
|
||||
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
|
||||
Properties:
|
||||
CloudFrontOriginAccessIdentityConfig:
|
||||
Comment: Visual Subnet Calculator Static Website Amazon CloudFront Identity
|
||||
StaticWebsite:
|
||||
Type: AWS::S3::Bucket
|
||||
Properties:
|
||||
BucketName: !Sub
|
||||
- visualsubnetcalc-static-website-${Unique}
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
AccessControl: Private
|
||||
LoggingConfiguration:
|
||||
DestinationBucketName: !Ref LoggingBucket
|
||||
LogFilePrefix: s3-static-website/
|
||||
PublicAccessBlockConfiguration:
|
||||
BlockPublicAcls: true
|
||||
BlockPublicPolicy: true
|
||||
IgnorePublicAcls: true
|
||||
RestrictPublicBuckets: true
|
||||
BucketEncryption:
|
||||
ServerSideEncryptionConfiguration:
|
||||
- ServerSideEncryptionByDefault:
|
||||
SSEAlgorithm: AES256
|
||||
VersioningConfiguration:
|
||||
Status: Enabled
|
||||
LifecycleConfiguration:
|
||||
Rules:
|
||||
- Id: DeleteOldVersionAfter7Days
|
||||
Status: Enabled
|
||||
NoncurrentVersionExpiration:
|
||||
NoncurrentDays: 7
|
||||
StaticWebsiteBucketPolicy:
|
||||
Type: AWS::S3::BucketPolicy
|
||||
Properties:
|
||||
Bucket: !Sub
|
||||
- visualsubnetcalc-static-website-${Unique}
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
PolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Id: WebAccess
|
||||
Statement:
|
||||
- Sid: CloudFrontReadForGetBucketObjects
|
||||
Principal:
|
||||
AWS: !Sub 'arn:${AWS::Partition}:iam::cloudfront:user/CloudFront Origin Access Identity ${OriginAccessIdentity}'
|
||||
Effect: Allow
|
||||
Action:
|
||||
- s3:GetObject
|
||||
- s3:GetObjectVersion
|
||||
Resource: !Sub
|
||||
- arn:${AWS::Partition}:s3:::visualsubnetcalc-static-website-${Unique}/*
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
- Sid: DenyPlaintextAccess
|
||||
Principal: '*'
|
||||
Effect: Deny
|
||||
Action: s3:*
|
||||
Resource:
|
||||
- !Sub
|
||||
- arn:${AWS::Partition}:s3:::visualsubnetcalc-static-website-${Unique}
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
- !Sub
|
||||
- arn:${AWS::Partition}:s3:::visualsubnetcalc-static-website-${Unique}/*
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
Condition:
|
||||
Bool:
|
||||
aws:SecureTransport: 'false'
|
||||
LoggingBucket:
|
||||
Type: AWS::S3::Bucket
|
||||
DeletionPolicy: Retain
|
||||
Properties:
|
||||
BucketName: !Sub
|
||||
- visualsubnetcalc-logging-${Unique}
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
AccessControl: Private
|
||||
PublicAccessBlockConfiguration:
|
||||
BlockPublicAcls: true
|
||||
BlockPublicPolicy: true
|
||||
IgnorePublicAcls: true
|
||||
RestrictPublicBuckets: true
|
||||
OwnershipControls:
|
||||
Rules:
|
||||
- ObjectOwnership: BucketOwnerPreferred
|
||||
BucketEncryption:
|
||||
ServerSideEncryptionConfiguration:
|
||||
- ServerSideEncryptionByDefault:
|
||||
SSEAlgorithm: AES256
|
||||
VersioningConfiguration:
|
||||
Status: Enabled
|
||||
LifecycleConfiguration:
|
||||
Rules:
|
||||
- Id: DeleteOldVersionAfter7Days
|
||||
Status: Enabled
|
||||
NoncurrentVersionExpiration:
|
||||
NoncurrentDays: 7
|
||||
LoggingBucketBucketPolicy:
|
||||
Type: AWS::S3::BucketPolicy
|
||||
DependsOn: LoggingBucket
|
||||
Properties:
|
||||
Bucket: !Sub
|
||||
- visualsubnetcalc-logging-${Unique}
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
PolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Id: WebAccess
|
||||
Statement:
|
||||
- Sid: S3ServerAccessLogsPolicy
|
||||
Effect: Allow
|
||||
Principal:
|
||||
Service: logging.s3.amazonaws.com
|
||||
Action:
|
||||
- s3:PutObject
|
||||
Resource: !Sub
|
||||
- arn:${AWS::Partition}:s3:::visualsubnetcalc-logging-${Unique}/*
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
Condition:
|
||||
ArnEquals:
|
||||
aws:SourceArn:
|
||||
- !Sub
|
||||
- arn:${AWS::Partition}:s3:::visualsubnetcalc-static-website-${Unique}
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
StringEquals:
|
||||
aws:SourceAccount: !Ref 'AWS::AccountId'
|
||||
- Sid: DenyPlaintextAccess
|
||||
Principal: '*'
|
||||
Effect: Deny
|
||||
Action: s3:*
|
||||
Resource:
|
||||
- !Sub
|
||||
- arn:${AWS::Partition}:s3:::visualsubnetcalc-logging-${Unique}
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
- !Sub
|
||||
- arn:${AWS::Partition}:s3:::visualsubnetcalc-logging-${Unique}/*
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
Condition:
|
||||
Bool:
|
||||
aws:SecureTransport: 'false'
|
||||
HostedZone:
|
||||
Type: AWS::Route53::HostedZone
|
||||
Properties:
|
||||
HostedZoneConfig:
|
||||
Comment: !Sub 'VisualSubnetCalc'
|
||||
Name: !Ref 'DomainName'
|
||||
Certificate:
|
||||
Type: AWS::CertificateManager::Certificate
|
||||
Properties:
|
||||
DomainName: !Ref 'DomainName'
|
||||
DomainValidationOptions:
|
||||
- DomainName: !Ref 'DomainName'
|
||||
HostedZoneId: !Ref 'HostedZone'
|
||||
ValidationMethod: DNS
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: !Sub 'VisualSubnetCalc'
|
||||
CloudFrontAlias:
|
||||
Type: AWS::Route53::RecordSet
|
||||
Properties:
|
||||
AliasTarget:
|
||||
DNSName: !GetAtt 'CloudFront.DomainName'
|
||||
HostedZoneId: Z2FDTNDATAQYW2
|
||||
Comment: To CloudFront S3
|
||||
HostedZoneId: !Ref 'HostedZone'
|
||||
Name: !Sub '${DomainName}.'
|
||||
Type: A
|
||||
CloudFront:
|
||||
Type: AWS::CloudFront::Distribution
|
||||
Properties:
|
||||
DistributionConfig:
|
||||
Comment: Visual Subnet Calculator Static Website
|
||||
Aliases:
|
||||
- !Ref 'DomainName'
|
||||
Logging:
|
||||
Bucket: !GetAtt LoggingBucket.DomainName
|
||||
Prefix: cloudfront
|
||||
IncludeCookies: true
|
||||
DefaultCacheBehavior:
|
||||
ForwardedValues:
|
||||
QueryString: false
|
||||
TargetOriginId: !Sub 'S3-Static-Website'
|
||||
ViewerProtocolPolicy: redirect-to-https
|
||||
Compress: true
|
||||
DefaultRootObject: index.html
|
||||
Enabled: true
|
||||
HttpVersion: http2
|
||||
Origins:
|
||||
- DomainName: !Sub
|
||||
- visualsubnetcalc-static-website-${Unique}.s3.${AWS::Region}.amazonaws.com
|
||||
- Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ]
|
||||
Id: !Sub 'S3-Static-Website'
|
||||
S3OriginConfig:
|
||||
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${OriginAccessIdentity}'
|
||||
PriceClass: PriceClass_100
|
||||
IPV6Enabled: false
|
||||
ViewerCertificate:
|
||||
AcmCertificateArn: !Ref 'Certificate'
|
||||
MinimumProtocolVersion: TLSv1.2_2021
|
||||
SslSupportMethod: sni-only
|
40
src/package-lock.json
generated
Normal file
40
src/package-lock.json
generated
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "src",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
|
||||
"integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz",
|
||||
"integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
src/package.json
Normal file
5
src/package.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.2.3"
|
||||
}
|
||||
}
|
21
src/scss/custom.scss
Normal file
21
src/scss/custom.scss
Normal file
|
@ -0,0 +1,21 @@
|
|||
@import '../node_modules/bootstrap/scss/functions';
|
||||
|
||||
@import '../node_modules/bootstrap/scss/variables';
|
||||
|
||||
$new-breakpoints: (
|
||||
xxxl:1600px,
|
||||
xxxxl:1800px,
|
||||
//xxxxx:2000px,
|
||||
);
|
||||
|
||||
$grid-breakpoints: map-merge($grid-breakpoints, $new-breakpoints);
|
||||
|
||||
$new-container-max-widths: (
|
||||
xxxl:1520px,
|
||||
xxxxl:1720px,
|
||||
//xxxxxl:1920px
|
||||
);
|
||||
|
||||
$container-max-widths: map-merge($container-max-widths, $new-container-max-widths);
|
||||
|
||||
@import "../node_modules/bootstrap/scss/bootstrap";
|
Loading…
Reference in a new issue