Merge branch 'release/v0.3'

This commit is contained in:
Caesar Kabalan 2023-05-18 01:42:29 -04:00
commit a68cc82dd4
No known key found for this signature in database
GPG key ID: DDFEF5FF6CFAB608
11 changed files with 824 additions and 1 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.idea/*
**/node_modules/*

View file

@ -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

File diff suppressed because one or more lines are too long

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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,5 @@
{
"dependencies": {
"bootstrap": "^5.2.3"
}
}

21
src/scss/custom.scss Normal file
View 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";