Merge branch 'release/v1.3.0'
This commit is contained in:
commit
8bacdbfafd
27 changed files with 2142 additions and 573 deletions
|
@ -1,38 +1,33 @@
|
||||||
name: docker
|
name: Docker Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
#push:
|
||||||
branches:
|
# branches:
|
||||||
- 'develop'
|
# - 'develop'
|
||||||
- 'main'
|
# - 'main'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOCKERHUB_TAG: ${{ github.ref_name == 'main' && 'latest' || 'develop' }}
|
DOCKERHUB_TAG: ${{ github.ref_name == 'main' && 'latest' || 'develop' }}
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Set up QEMU
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
-
|
- name: Set up Docker Buildx
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
-
|
- name: Login to Docker Hub
|
||||||
name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
- id: container_name
|
||||||
id: container_name
|
|
||||||
uses: ASzc/change-string-case-action@v5
|
uses: ASzc/change-string-case-action@v5
|
||||||
with:
|
with:
|
||||||
string: ${{ github.repository_owner }}/${{ github.event.repository.name }}
|
string: ${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||||
-
|
- name: Build and push
|
||||||
name: Build and push
|
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
43
.github/workflows/ui-testing.yml
vendored
Normal file
43
.github/workflows/ui-testing.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
name: UI Testing (Playwright)
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
timeout-minutes: 30
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
- run: |
|
||||||
|
export MKCERT_VERSION=v1.4.4
|
||||||
|
export PLATFORM=linux
|
||||||
|
curl -L "https://github.com/FiloSottile/mkcert/releases/download/$MKCERT_VERSION/mkcert-$MKCERT_VERSION-$PLATFORM-amd64" -o mkcert
|
||||||
|
chmod +x mkcert
|
||||||
|
echo "$PWD/mkcert" >> $GITHUB_PATH
|
||||||
|
mkdir -p src/certs
|
||||||
|
./mkcert -cert-file src/certs/cert.pem -key-file src/certs/cert.key localhost 127.0.0.1
|
||||||
|
- run: npm ci
|
||||||
|
working-directory: ./src
|
||||||
|
- run: npm run build --if-present
|
||||||
|
working-directory: ./src
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install --with-deps
|
||||||
|
working-directory: ./src
|
||||||
|
- name: Run Playwright tests
|
||||||
|
run: npm test
|
||||||
|
working-directory: ./src
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: ./src/playwright-report
|
||||||
|
retention-days: 30
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
.idea/*
|
.idea/*
|
||||||
**/node_modules/*
|
**/node_modules/*
|
||||||
**/certs/*
|
**/certs/*
|
||||||
|
**/.DS_Store
|
||||||
|
|
107
.repopackignore
Normal file
107
.repopackignore
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
# VisualSubnetCalc Custom
|
||||||
|
dist/css/bootstrap.*
|
||||||
|
src/cloudformation.yaml
|
||||||
|
|
||||||
|
|
||||||
|
# RepoPack Defaults
|
||||||
|
# Version control
|
||||||
|
.git/**
|
||||||
|
.hg/**
|
||||||
|
.hgignore
|
||||||
|
.svn/**
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/**
|
||||||
|
**/node_modules/**
|
||||||
|
bower_components/**
|
||||||
|
**/bower_components/**
|
||||||
|
jspm_packages/**
|
||||||
|
**/jspm_packages/**
|
||||||
|
vendor/**
|
||||||
|
.bundle/**
|
||||||
|
.gradle/**
|
||||||
|
target/**
|
||||||
|
# Logs
|
||||||
|
logs/**
|
||||||
|
**/*.log
|
||||||
|
**/npm-debug.log*
|
||||||
|
**/yarn-debug.log*
|
||||||
|
**/yarn-error.log*
|
||||||
|
# Runtime data
|
||||||
|
pids/**
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov/**
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/**
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output/**
|
||||||
|
# Grunt intermediate storage
|
||||||
|
.grunt/**
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
# Compiled binary addons
|
||||||
|
build/Release/**
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/**
|
||||||
|
# Optional npm cache directory
|
||||||
|
**/.npm/**
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
# Yarn files
|
||||||
|
**/.yarn/**
|
||||||
|
# Yarn Integrity file
|
||||||
|
**/.yarn-integrity
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
# next.js build output
|
||||||
|
.next/**
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt/**
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist/**
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/**
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/**
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/**
|
||||||
|
# TypeScript output
|
||||||
|
#dist/**
|
||||||
|
# OS generated files
|
||||||
|
**/.DS_Store
|
||||||
|
**/Thumbs.db
|
||||||
|
# Editor directories and files
|
||||||
|
.idea/**
|
||||||
|
.vscode/**
|
||||||
|
**/*.swp
|
||||||
|
**/*.swo
|
||||||
|
**/*.swn
|
||||||
|
**/*.bak
|
||||||
|
# Package manager locks
|
||||||
|
**/package-lock.json
|
||||||
|
**/yarn.lock
|
||||||
|
**/pnpm-lock.yaml
|
||||||
|
# Build outputs
|
||||||
|
build/**
|
||||||
|
out/**
|
||||||
|
# Temporary files
|
||||||
|
tmp/**
|
||||||
|
temp/**
|
||||||
|
# repopack output
|
||||||
|
repopack-output.txt
|
||||||
|
# Essential Python-related entries
|
||||||
|
**/__pycache__/**
|
||||||
|
**/*.py[cod]
|
||||||
|
**/venv/**
|
||||||
|
**/.venv/**
|
||||||
|
**/.pytest_cache/**
|
||||||
|
**/.mypy_cache/**
|
||||||
|
**/.ipynb_checkpoints/**
|
||||||
|
**/Pipfile.lock
|
||||||
|
**/poetry.lock
|
|
@ -11,3 +11,4 @@ RUN npm run build
|
||||||
|
|
||||||
FROM nginx
|
FROM nginx
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
|
1
LICENSE
1
LICENSE
|
@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
57
NOTES.md
Normal file
57
NOTES.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
## Efficient Saves
|
||||||
|
|
||||||
|
Looks like the most efficient way to store an address is to track the base network (say 10.0.0.0/8) and then represent
|
||||||
|
all other addresses as offsets from that base network. This way, we can store a subnet as a base network and a mask. The
|
||||||
|
most efficient way to store this is to store two values:
|
||||||
|
|
||||||
|
- The base network offset (0 to 4,294,967,296)
|
||||||
|
- 0 being the best case, a subnet within the base subnet with the same network address (10.0.0.0/24)
|
||||||
|
- 255.255.255.255 being the worst case, the last address in 0.0.0.0/0 (unrealistic)
|
||||||
|
- The mask (0 to 32)
|
||||||
|
|
||||||
|
Combine both of these values into a single binary string that is 32+5 bits. Round the storage up to the nearest byte
|
||||||
|
(40 bits = 5 bytes), padding the remaining bits with 0s, then encode this 5 byte string into a URL-safe base64 string.
|
||||||
|
|
||||||
|
Example for 10.0.0.0/8 and representing the network 10.0.15.0/24:
|
||||||
|
Find the offset:
|
||||||
|
10.0.0.0 (decimal):
|
||||||
|
00001010 00000000 00000000 00000000 → 167772160
|
||||||
|
10.0.15.0 (decimal):
|
||||||
|
00001010 00000000 00001111 00000000 → 167775232
|
||||||
|
Offset: 167775232 - 167772160 = 3072
|
||||||
|
|
||||||
|
Hmmm, this above works good for close together smaller networks but gets ugly when you're dealing with larger networks
|
||||||
|
because the offset is huge.
|
||||||
|
|
||||||
|
I'm thinking about a coordinate system. Let's say you have a base network of 10.0.0.0/8 and you want to as concisely as
|
||||||
|
possible represent 10.166.64.0/20. We could represent this as the nth /20 within the /8 and just store N-20, and
|
||||||
|
probably shorten that even more with the last digit always being base32 for the mask.
|
||||||
|
|
||||||
|
If I'm doing my math right you can say a /20 has 4096 addresses.
|
||||||
|
|
||||||
|
10.0.0.0 = 167772160
|
||||||
|
10.166.64.0 = 178667520
|
||||||
|
178667520 - 167772160 = 10895360
|
||||||
|
10895360 / 4096 = 2660
|
||||||
|
Which means 10.166.64.0 is the 2,660th /20 within the /8. You could store this as 2660x20 (inefficient)
|
||||||
|
|
||||||
|
You can reverse this 2660x20 from 10.0.0.0/8 by doing the following:
|
||||||
|
a /21 has 4096 addresses, times the 2660th network is 10895360. Add that to the base network address to get the network
|
||||||
|
|
||||||
|
10.0.0.0 = 167772160
|
||||||
|
167772160 + 10895360 = 178667520
|
||||||
|
178667520 = 10.166.64.0 then you can tack back on the /20
|
||||||
|
|
||||||
|
This has the advantage overall that you're unlikely to store wildly different subnet sizes in the same page. Your "N" in
|
||||||
|
the "Nth" subnet is likely to be very small. You're more likely to store the Nth /24 in a /20 than you are a /20 in an
|
||||||
|
/8. So the numbers will mostly be 1-2 digits, rarely 3 digits, and almost never 4 digits as depicted above.
|
||||||
|
|
||||||
|
I then for efficienty I could use this format:
|
||||||
|
|
||||||
|
`[Nth Network as Integer][Network Size as Base32]`
|
||||||
|
|
||||||
|
So lets say you're wanting to represent the 0th /24 in a /20 you would represent it as `00`, always knowing the last
|
||||||
|
digit is the network size. Or the 0th /32 would be `07` (32 in base32 is 7). or the 5th /28 would be `54`.
|
||||||
|
|
|
@ -33,10 +33,12 @@ Compile from source:
|
||||||
```shell
|
```shell
|
||||||
# Clone the repository
|
# Clone the repository
|
||||||
> git clone https://github.com/ckabalan/visualsubnetcalc
|
> git clone https://github.com/ckabalan/visualsubnetcalc
|
||||||
|
# Change to the repository directory
|
||||||
|
> cd visualsubnetcalc
|
||||||
# Use recommended NVM version
|
# Use recommended NVM version
|
||||||
> nvm use
|
> nvm use
|
||||||
# Change to the sources directory
|
# Change to the sources directory
|
||||||
> cd visualsubnetcalc/src
|
> cd src
|
||||||
# Install Bootstrap
|
# Install Bootstrap
|
||||||
> npm install
|
> npm install
|
||||||
# Compile Bootstrap (Also install sass command line globally)
|
# Compile Bootstrap (Also install sass command line globally)
|
||||||
|
@ -105,3 +107,4 @@ Split icon made by [Freepik](https://www.flaticon.com/authors/freepik) from [Fla
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Visual Subnet Calculator is released under the [MIT License](https://opensource.org/licenses/MIT)
|
Visual Subnet Calculator is released under the [MIT License](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
|
6
dist/css/bootstrap.min.css
vendored
6
dist/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
2
dist/css/bootstrap.min.css.map
vendored
2
dist/css/bootstrap.min.css.map
vendored
File diff suppressed because one or more lines are too long
15
dist/css/main.css
vendored
15
dist/css/main.css
vendored
|
@ -51,6 +51,21 @@
|
||||||
border-bottom:1px black dotted;
|
border-bottom:1px black dotted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#whats_new {
|
||||||
|
cursor:pointer !important;
|
||||||
|
text-align: right;
|
||||||
|
width:15rem;
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#whats_new a {
|
||||||
|
width:15rem;
|
||||||
|
text-align: right;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom:1px var(--bs-success) dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#copy_url {
|
#copy_url {
|
||||||
cursor:pointer !important;
|
cursor:pointer !important;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
78
dist/index.html
vendored
78
dist/index.html
vendored
|
@ -19,12 +19,12 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container-xxl mt-3">
|
<div class="container-xxl mt-3">
|
||||||
<div class="float-end" id="navigation">
|
<div class="float-end" id="navigation">
|
||||||
<a href="#" id="info_icon" data-bs-toggle="modal" data-bs-target="#aboutModal">
|
<a href="#" id="info_icon" data-bs-toggle="modal" data-bs-target="#aboutModal" aria-label="About">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16">
|
<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 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"/>
|
<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>
|
</svg>
|
||||||
</a><a href="https://github.com/ckabalan/visualsubnetcalc" target="_blank">
|
</a><a href="https://github.com/ckabalan/visualsubnetcalc" target="_blank" aria-label="GitHub">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16">
|
<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"/>
|
<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>
|
</svg>
|
||||||
|
@ -95,78 +95,80 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<table id="calc" class="table table-bordered font-monospace">
|
<table id="calc" class="table table-bordered font-monospace" aria-label="Subnet Table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th id="subnetHeader" style="display: table-cell;">Subnet address</th>
|
<th aria-label="Subnet Address" id="subnetHeader" style="display: table-cell;">Subnet Address</th>
|
||||||
<th id="netmaskHeader" style="display: none;">Netmask</th>
|
<th aria-label="Range of Addresses" id="rangeHeader" style="display: table-cell;">Range of Addresses</th>
|
||||||
<th id="rangeHeader" style="display: table-cell;">Range of addresses</th>
|
<th aria-label="Usable IPs" id="useableHeader" style="display: table-cell;">Usable IPs</th>
|
||||||
<th id="useableHeader" style="display: table-cell;">Usable IPs</th>
|
<th aria-label="Hosts" id="hostsHeader" style="display: table-cell;">Hosts</th>
|
||||||
<th id="hostsHeader" style="display: table-cell;">Hosts</th>
|
<th aria-label="Note" id="noteHeader" colspan="100%" style="display: table-cell;">
|
||||||
<th id="noteHeader" colspan="100%" style="display: table-cell;">
|
|
||||||
Note
|
Note
|
||||||
<div style="float:right;"><span class="split">Split</span>/<span class="join">Join</span></div>
|
<div style="float:right;"><span id="splitHeader" aria-label="Split" class="split">Split</span>/<span id="joinHeader" aria-label="Join" class="join">Join</span></div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="calcbody">
|
<tbody id="calcbody">
|
||||||
<tr id="row_10-0-0-0_17">
|
<tr id="row_10-0-0-0_16" aria-label="10.0.0.0/16">
|
||||||
<td class="row_address">Loading...</td>
|
<td aria-labelledby="subnetHeader" class="row_address">Loading...</td>
|
||||||
<td class="row_range"></td>
|
<td aria-labelledby="rangeHeader" class="row_range"></td>
|
||||||
<td class="row_usable"></td>
|
<td aria-labelledby="useableHeader" class="row_usable"></td>
|
||||||
<td class="row_hosts"></td>
|
<td aria-labelledby="hostsHeader" class="row_hosts"></td>
|
||||||
<td class="note"><label><input type="text" class="form-control shadow-none p-0"></label></td>
|
<td class="note"><label><input aria-labelledby="noteHeader" type="text" class="form-control shadow-none p-0"></label></td>
|
||||||
<td rowspan="1" colspan="13" class="split rotate"><span></span></td>
|
<td aria-labelledby="splitHeader" rowspan="1" colspan="13" class="split rotate"><span></span></td>
|
||||||
<td rowspan="14" colspan="1" class="join rotate"><span></span></td>
|
<td aria-labelledby="joinHeader" rowspan="14" colspan="1" class="join rotate"><span></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div id="bottom_nav">
|
<div id="bottom_nav">
|
||||||
<div class="d-inline-block align-top pt-1" id="colors_word_open"><span>Change Colors »</span></div>
|
<div class="d-inline-block align-top pt-1" id="colors_word_open" aria-label="Change Colors"><span>Change Colors »</span></div>
|
||||||
<div class="d-inline-block d-none" id="color_palette">
|
<div class="d-inline-block d-none" id="color_palette" aria-label="Color Palette">
|
||||||
<div id="palette_picker_1"></div>
|
<div role="button" aria-label="Color 1" id="palette_picker_1"></div>
|
||||||
<div id="palette_picker_2"></div>
|
<div role="button" aria-label="Color 2" id="palette_picker_2"></div>
|
||||||
<div id="palette_picker_3"></div>
|
<div role="button" aria-label="Color 3" id="palette_picker_3"></div>
|
||||||
<div id="palette_picker_4"></div>
|
<div role="button" aria-label="Color 4" id="palette_picker_4"></div>
|
||||||
<div id="palette_picker_5"></div>
|
<div role="button" aria-label="Color 5" id="palette_picker_5"></div>
|
||||||
<div id="palette_picker_6"></div>
|
<div role="button" aria-label="Color 6" id="palette_picker_6"></div>
|
||||||
<div id="palette_picker_7"></div>
|
<div role="button" aria-label="Color 7" id="palette_picker_7"></div>
|
||||||
<div id="palette_picker_8"></div>
|
<div role="button" aria-label="Color 8" id="palette_picker_8"></div>
|
||||||
<div id="palette_picker_9"></div>
|
<div role="button" aria-label="Color 9" id="palette_picker_9"></div>
|
||||||
<div id="palette_picker_10"></div>
|
<div role="button" aria-label="Color 10" id="palette_picker_10"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-inline-block align-top align-top pt-1 ps-2 d-none" id="colors_word_close"><span>« Stop Changing Colors</span></div>
|
<div class="d-inline-block align-top align-top pt-1 ps-2 d-none" id="colors_word_close" aria-label="Stop Changing Colors"><span>« Stop Changing Colors</span></div>
|
||||||
<div class="d-inline-block align-top pt-1 ps-3" id="copy_url"><span>Copy Shareable URL</span></div>
|
<div class="d-inline-block align-top pt-1 ps-3" id="copy_url"><span>Copy Shareable URL</span></div>
|
||||||
|
<div class="d-inline-block align-top pt-1 ps-3" id="whats_new">
|
||||||
|
<a title="Released 2024-10-15" class="link-success" href="https://github.com/ckabalan/visualsubnetcalc/releases/tag/v1.3.0" target="_blank" aria-label="What's New">v1.3.0 (What's New?)</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="modal fade" id="notifyModal" tabindex="-1" aria-labelledby="notifyModalLabel" aria-hidden="true">
|
<div class="modal fade" id="notifyModal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-md">
|
<div class="modal-dialog modal-md">
|
||||||
<div class="modal-content alert-warning">
|
<div class="modal-content alert-warning" role="alertdialog" aria-labelledby="notifyModalLabel" aria-describedby="notifyModalDescription">
|
||||||
<div class="modal-header border-bottom-0 pb-1">
|
<div class="modal-header border-bottom-0 pb-1">
|
||||||
<h3 class="modal-title" id="notifyModalLabel">Warning!</h3>
|
<h3 class="modal-title" id="notifyModalLabel">Warning!</h3>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body pt-1">
|
<div class="modal-body pt-1" id="notifyModalDescription">
|
||||||
Notification Text Here
|
Notification Text Here
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="importExportModal" tabindex="-1" aria-labelledby="importExportModalLabel" aria-hidden="true">
|
<div class="modal fade" id="importExportModal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content" role="alertdialog" aria-labelledby="importExportModalLabel" aria-describedby="importExportModalDescription">
|
||||||
<div class="modal-header border-bottom-0 pb-1">
|
<div class="modal-header border-bottom-0 pb-1">
|
||||||
<h3 class="modal-title" id="importExportModalLabel">Import/Export</h3>
|
<h3 class="modal-title" id="importExportModalLabel">Import/Export</h3>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body pt-1">
|
<div class="modal-body pt-1">
|
||||||
<div class="alert alert-primary show mt-3" role="alert">
|
<div class="alert alert-primary show mt-3" id="importExportModalDescription">
|
||||||
Copy the content from the box below to EXPORT the current subnet configuration. Or, overwrite/paste a previously exported configuration into the box below and click IMPORT.
|
Copy the content from the box below to EXPORT the current subnet configuration. Or, overwrite/paste a previously exported configuration into the box below and click IMPORT.
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating font-monospace">
|
<div class="form-floating font-monospace">
|
||||||
<textarea class="form-control pt-3" id="importExportArea" style="height: 510px"></textarea>
|
<textarea class="form-control pt-3" id="importExportArea" style="height: 510px" aria-label="Import/Export Content"></textarea>
|
||||||
<label for="importExportArea"></label>
|
<label for="importExportArea"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
280
dist/js/main.js
vendored
280
dist/js/main.js
vendored
|
@ -28,7 +28,7 @@ let operatingMode = 'Standard'
|
||||||
let previousOperatingMode = 'Standard'
|
let previousOperatingMode = 'Standard'
|
||||||
let inflightColor = 'NONE'
|
let inflightColor = 'NONE'
|
||||||
let urlVersion = '1'
|
let urlVersion = '1'
|
||||||
let configVersion = '1'
|
let configVersion = '2'
|
||||||
|
|
||||||
const netsizePatterns = {
|
const netsizePatterns = {
|
||||||
Standard: '^([12]?[0-9]|3[0-2])$',
|
Standard: '^([12]?[0-9]|3[0-2])$',
|
||||||
|
@ -42,12 +42,27 @@ const minSubnetSizes = {
|
||||||
AWS: 28,
|
AWS: 28,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$('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()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('input#network,input#netsize').on('input', function() {
|
$('input#network,input#netsize').on('input', function() {
|
||||||
$('#input_form')[0].classList.add('was-validated');
|
$('#input_form')[0].classList.add('was-validated');
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
$('#color_palette div').on('click', function() {
|
$('#color_palette div').on('click', function() {
|
||||||
// We don't really NEED to convert this to hex, but it's really low overhead to do the
|
// 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
|
// conversion here and saves us space in the export/save
|
||||||
|
@ -138,7 +153,7 @@ $('#bottom_nav #copy_url').on('click', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
$('#btn_import_export').on('click', function() {
|
$('#btn_import_export').on('click', function() {
|
||||||
$('#importExportArea').val(JSON.stringify(exportConfig(), null, 2))
|
$('#importExportArea').val(JSON.stringify(exportConfig(false), null, 2))
|
||||||
})
|
})
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
|
@ -150,14 +165,47 @@ function reset() {
|
||||||
let rootCidr = rootNetwork + '/' + $('#netsize').val()
|
let rootCidr = rootNetwork + '/' + $('#netsize').val()
|
||||||
if (cidrInput !== rootCidr) {
|
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>')
|
show_warning_modal('<div>Your network input is not on a network boundary for this network size. It has been automatically changed:</div><div class="font-monospace pt-2">' + $('#network').val() + ' -> ' + rootNetwork + '</div>')
|
||||||
|
$('#network').val(rootNetwork)
|
||||||
|
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] = {}
|
||||||
}
|
}
|
||||||
$('#network').val(rootNetwork)
|
|
||||||
subnetMap = {}
|
|
||||||
subnetMap[rootCidr] = {}
|
|
||||||
maxNetSize = parseInt($('#netsize').val())
|
maxNetSize = parseInt($('#netsize').val())
|
||||||
renderTable(operatingMode);
|
renderTable(operatingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
$('#calcbody').on('click', 'td.split,td.join', function(event) {
|
$('#calcbody').on('click', 'td.split,td.join', function(event) {
|
||||||
// HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags
|
// HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags
|
||||||
mutate_subnet_map(this.dataset.mutateVerb, this.dataset.subnet, '')
|
mutate_subnet_map(this.dataset.mutateVerb, this.dataset.subnet, '')
|
||||||
|
@ -228,15 +276,16 @@ function addRow(network, netSize, colspan, note, notesWidth, color, operatingMod
|
||||||
rangeCol = int2ip(addressFirst);
|
rangeCol = int2ip(addressFirst);
|
||||||
usableCol = int2ip(usableFirst);
|
usableCol = int2ip(usableFirst);
|
||||||
}
|
}
|
||||||
|
let rowId = 'row_' + network.replace('.', '-') + '_' + netSize
|
||||||
|
let rowCIDR = network + '/' + netSize
|
||||||
let newRow =
|
let newRow =
|
||||||
' <tr id="row_' + network.replace('.', '-') + '_' + netSize + '"' + styleTag + '>\n' +
|
' <tr id="' + rowId + '"' + styleTag + ' aria-label="' + rowCIDR + '">\n' +
|
||||||
' <td data-subnet="' + network + '/' + netSize + '" class="row_address">' + network + '/' + netSize + '</td>\n' +
|
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' subnetHeader" class="row_address">' + rowCIDR + '</td>\n' +
|
||||||
' <td data-subnet="' + network + '/' + netSize + '" class="row_range">' + rangeCol + '</td>\n' +
|
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' rangeHeader" class="row_range">' + rangeCol + '</td>\n' +
|
||||||
' <td data-subnet="' + network + '/' + netSize + '" class="row_usable">' + usableCol + '</td>\n' +
|
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' useableHeader" class="row_usable">' + usableCol + '</td>\n' +
|
||||||
' <td data-subnet="' + network + '/' + netSize + '" class="row_hosts">' + hostCount + '</td>\n' +
|
' <td data-subnet="' + rowCIDR + '" aria-labelledby="' + rowId + ' hostsHeader" class="row_hosts">' + hostCount + '</td>\n' +
|
||||||
' <td class="note" style="width:' + notesWidth + '"><label><input type="text" class="form-control shadow-none p-0" data-subnet="' + network + '/' + netSize + '" value="' + note + '"></label></td>\n' +
|
' <td class="note" style="width:' + notesWidth + '"><label><input aria-labelledby="' + rowId + ' noteHeader" type="text" class="form-control shadow-none p-0" data-subnet="' + rowCIDR + '" value="' + note + '"></label></td>\n' +
|
||||||
' <td rowspan="1" colspan="' + colspan + '" class="split rotate" data-subnet="' + network + '/' + netSize + '" data-mutate-verb="split"><span>/' + netSize + '</span></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'
|
||||||
if (netSize > maxNetSize) {
|
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
|
// 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.
|
// of ancestors so you can set colspan.
|
||||||
|
@ -247,7 +296,7 @@ function addRow(network, netSize, colspan, note, notesWidth, color, operatingMod
|
||||||
for (const i in matchingNetworkList) {
|
for (const i in matchingNetworkList) {
|
||||||
let matchingNetwork = matchingNetworkList[i]
|
let matchingNetwork = matchingNetworkList[i]
|
||||||
let networkChildrenCount = count_network_children(matchingNetwork, subnetMap, [])
|
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 += ' <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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newRow += ' </tr>';
|
newRow += ' </tr>';
|
||||||
|
@ -262,7 +311,93 @@ function ip2int(ip) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function int2ip (ipInt) {
|
function int2ip (ipInt) {
|
||||||
return ( (ipInt>>>24) +'.' + (ipInt>>16 & 255) +'.' + (ipInt>>8 & 255) +'.' + (ipInt & 255) );
|
return ((ipInt>>>24) + '.' + (ipInt>>16 & 255) + '.' + (ipInt>>8 & 255) + '.' + (ipInt & 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBase36(num) {
|
||||||
|
return num.toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromBase36(str) {
|
||||||
|
return parseInt(str, 36);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 9th /31 subnet within the /27
|
||||||
|
* - 'v' represents the /31 mask size converted to Base 36 (31 -> 'v')
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
return `${nthSubnet}${toBase36(parseInt(specificMask, 10))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
// '10.0.96.0/20' being the 7th /20 (base36 'k' is 20 int) within the /16.
|
||||||
|
function getSubnetFromNth(baseNetwork, nthString) {
|
||||||
|
const [baseIp, baseMask] = baseNetwork.split('/');
|
||||||
|
const baseInt = ip2int(baseIp);
|
||||||
|
|
||||||
|
const size = fromBase36(nthString.slice(-1));
|
||||||
|
const nth = parseInt(nthString.slice(0, -1), 10);
|
||||||
|
|
||||||
|
const innerSizeInt = 32 - size;
|
||||||
|
const subnetInt = baseInt + (nth << innerSizeInt);
|
||||||
|
|
||||||
|
return `${int2ip(subnetInt)}/${size}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function subnet_last_address(subnet, netSize) {
|
function subnet_last_address(subnet, netSize) {
|
||||||
|
@ -320,7 +455,7 @@ function has_network_sub_keys(dict) {
|
||||||
let allKeys = Object.keys(dict)
|
let allKeys = Object.keys(dict)
|
||||||
// Maybe an efficient way to do this with a Lambda?
|
// Maybe an efficient way to do this with a Lambda?
|
||||||
for (let i in allKeys) {
|
for (let i in allKeys) {
|
||||||
if (!allKeys[i].startsWith('_')) {
|
if (!allKeys[i].startsWith('_') && allKeys[i] !== 'n' && allKeys[i] !== 'c') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -612,25 +747,25 @@ $( document ).ready(function() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
errorPlacement: function(error, element) {
|
errorPlacement: function(error, element) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
console.log(element);
|
//console.log(element);
|
||||||
if (error[0].innerHTML !== '') {
|
if (error[0].innerHTML !== '') {
|
||||||
console.log('Error Placement - Text')
|
//console.log('Error Placement - Text')
|
||||||
if (!element.data('errorIsVisible')) {
|
if (!element.data('errorIsVisible')) {
|
||||||
bootstrap.Tooltip.getInstance(element).setContent({'.tooltip-inner': error[0].innerHTML})
|
bootstrap.Tooltip.getInstance(element).setContent({'.tooltip-inner': error[0].innerHTML})
|
||||||
element.tooltip('show');
|
element.tooltip('show');
|
||||||
element.data('errorIsVisible', true)
|
element.data('errorIsVisible', true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Error Placement - Empty')
|
//console.log('Error Placement - Empty')
|
||||||
console.log(element);
|
//console.log(element);
|
||||||
if (element.data('errorIsVisible')) {
|
if (element.data('errorIsVisible')) {
|
||||||
element.tooltip('hide');
|
element.tooltip('hide');
|
||||||
element.data('errorIsVisible', false)
|
element.data('errorIsVisible', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
console.log(element);
|
//console.log(element);
|
||||||
},
|
},
|
||||||
// This success function appears to be required as errorPlacement() does not fire without the success function
|
// This success function appears to be required as errorPlacement() does not fire without the success function
|
||||||
// being defined.
|
// being defined.
|
||||||
|
@ -646,33 +781,40 @@ $( document ).ready(function() {
|
||||||
if (!autoConfigResult) {
|
if (!autoConfigResult) {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
//importConfig('{"config_version":"1","subnets":{"10.0.0.0/16":{"10.0.0.0/17":{"10.0.0.0/18":{},"10.0.64.0/18":{}},"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.160.0/21":{"10.0.160.0/22":{},"10.0.164.0/22":{}},"10.0.168.0/21":{}},"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":{}}}}},"notes":{}}')
|
|
||||||
//importConfig('{"config_version":"1","subnets":{"10.0.0.0/16":{"10.0.0.0/17":{"10.0.0.0/18":{"_note":"Note 1"},"10.0.64.0/18":{"_note":"Note 2"}},"10.0.128.0/17":{"10.0.128.0/18":{"10.0.128.0/19":{"_note":"Note 3"},"10.0.160.0/19":{"10.0.160.0/20":{"10.0.160.0/21":{"10.0.160.0/22":{"_note":"Note 4"},"10.0.164.0/22":{"_note":"Note 5"}},"10.0.168.0/21":{"_note":"Note 6"}},"10.0.176.0/20":{"10.0.176.0/21":{"10.0.176.0/22":{"10.0.176.0/23":{"_note":"Note 7"},"10.0.178.0/23":{"_note":"Note 8"}},"10.0.180.0/22":{"_note":"Note 9"}},"10.0.184.0/21":{"_note":"Note 10"}}}},"10.0.192.0/18":{"10.0.192.0/19":{"_note":"Note 11"},"10.0.224.0/19":{"_note":"Note 12"}}}}},"notes":{}}')
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function exportConfig() {
|
function exportConfig(isMinified = true) {
|
||||||
|
const baseNetwork = Object.keys(subnetMap)[0]
|
||||||
|
let miniSubnetMap = {};
|
||||||
|
if (isMinified) {
|
||||||
|
minifySubnetMap(miniSubnetMap, subnetMap, baseNetwork)
|
||||||
|
}
|
||||||
if (operatingMode !== 'Standard') {
|
if (operatingMode !== 'Standard') {
|
||||||
return {
|
return {
|
||||||
'config_version': configVersion,
|
'config_version': configVersion,
|
||||||
'operating_mode': operatingMode,
|
'operating_mode': operatingMode,
|
||||||
'subnets': subnetMap,
|
'base_network': baseNetwork,
|
||||||
|
'subnets': isMinified ? miniSubnetMap : subnetMap,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'config_version': configVersion,
|
'config_version': configVersion,
|
||||||
'subnets': subnetMap,
|
'base_network': baseNetwork,
|
||||||
|
'subnets': isMinified ? miniSubnetMap : subnetMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfigUrl() {
|
function getConfigUrl() {
|
||||||
let defaultExport = JSON.parse(JSON.stringify(exportConfig()));
|
// Deep Copy
|
||||||
|
let defaultExport = JSON.parse(JSON.stringify(exportConfig(true)));
|
||||||
renameKey(defaultExport, 'config_version', 'v')
|
renameKey(defaultExport, 'config_version', 'v')
|
||||||
|
renameKey(defaultExport, 'base_network', 'b')
|
||||||
if (defaultExport.hasOwnProperty('operating_mode')) {
|
if (defaultExport.hasOwnProperty('operating_mode')) {
|
||||||
renameKey(defaultExport, 'operating_mode', 'm')
|
renameKey(defaultExport, 'operating_mode', 'm')
|
||||||
}
|
}
|
||||||
renameKey(defaultExport, 'subnets', 's')
|
renameKey(defaultExport, 'subnets', 's')
|
||||||
shortenKeys(defaultExport['s'])
|
//console.log(JSON.stringify(defaultExport))
|
||||||
return '/index.html?c=' + urlVersion + LZString.compressToEncodedURIComponent(JSON.stringify(defaultExport))
|
return '/index.html?c=' + urlVersion + LZString.compressToEncodedURIComponent(JSON.stringify(defaultExport))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,31 +832,63 @@ function processConfigUrl() {
|
||||||
renameKey(urlConfig, 'm', 'operating_mode')
|
renameKey(urlConfig, 'm', 'operating_mode')
|
||||||
}
|
}
|
||||||
renameKey(urlConfig, 's', 'subnets')
|
renameKey(urlConfig, 's', 'subnets')
|
||||||
expandKeys(urlConfig['subnets'])
|
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
|
||||||
|
}
|
||||||
importConfig(urlConfig)
|
importConfig(urlConfig)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function shortenKeys(subnetTree) {
|
function minifySubnetMap(minifiedMap, referenceMap, baseNetwork) {
|
||||||
for (let mapKey in subnetTree) {
|
for (let subnet in referenceMap) {
|
||||||
if (mapKey.startsWith('_')) {
|
if (subnet.startsWith('_')) continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (has_network_sub_keys(subnetTree[mapKey])) {
|
|
||||||
shortenKeys(subnetTree[mapKey])
|
|
||||||
} else {
|
|
||||||
if (subnetTree[mapKey].hasOwnProperty('_note')) {
|
|
||||||
renameKey(subnetTree[mapKey], '_note', '_n')
|
|
||||||
}
|
|
||||||
if (subnetTree[mapKey].hasOwnProperty('_color')) {
|
|
||||||
renameKey(subnetTree[mapKey], '_color', '_c')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let subnetKey = getSubnetFromNth(baseNetwork, mapKey)
|
||||||
|
expandedMap[subnetKey] = {}
|
||||||
|
if (has_network_sub_keys(miniMap[mapKey])) {
|
||||||
|
expandSubnetMap(expandedMap[subnetKey], miniMap[mapKey], baseNetwork)
|
||||||
|
} else {
|
||||||
|
if (miniMap[mapKey].hasOwnProperty('n')) {
|
||||||
|
expandedMap[subnetKey]['_note'] = miniMap[mapKey]['n']
|
||||||
|
}
|
||||||
|
if (miniMap[mapKey].hasOwnProperty('c')) {
|
||||||
|
expandedMap[subnetKey]['_color'] = miniMap[mapKey]['c']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Config Version 1 Backwards Compatibility
|
||||||
function expandKeys(subnetTree) {
|
function expandKeys(subnetTree) {
|
||||||
for (let mapKey in subnetTree) {
|
for (let mapKey in subnetTree) {
|
||||||
if (mapKey.startsWith('_')) {
|
if (mapKey.startsWith('_')) {
|
||||||
|
@ -734,7 +908,6 @@ function expandKeys(subnetTree) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function renameKey(obj, oldKey, newKey) {
|
function renameKey(obj, oldKey, newKey) {
|
||||||
if (oldKey !== newKey) {
|
if (oldKey !== newKey) {
|
||||||
Object.defineProperty(obj, newKey,
|
Object.defineProperty(obj, newKey,
|
||||||
|
@ -745,13 +918,16 @@ function renameKey(obj, oldKey, newKey) {
|
||||||
|
|
||||||
function importConfig(text) {
|
function importConfig(text) {
|
||||||
if (text['config_version'] === '1') {
|
if (text['config_version'] === '1') {
|
||||||
let subnet_split = Object.keys(text['subnets'])[0].split('/')
|
var [subnetNet, subnetSize] = Object.keys(text['subnets'])[0].split('/')
|
||||||
$('#network').val(subnet_split[0])
|
} else if (text['config_version'] === '2') {
|
||||||
$('#netsize').val(subnet_split[1])
|
var [subnetNet, subnetSize] = text['base_network'].split('/')
|
||||||
subnetMap = text['subnets'];
|
|
||||||
operatingMode = text['operating_mode'] || 'Standard'
|
|
||||||
switchMode(operatingMode);
|
|
||||||
}
|
}
|
||||||
|
$('#network').val(subnetNet)
|
||||||
|
$('#netsize').val(subnetSize)
|
||||||
|
subnetMap = text['subnets'];
|
||||||
|
operatingMode = text['operating_mode'] || 'Standard'
|
||||||
|
switchMode(operatingMode);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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('')}`
|
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('')}`
|
||||||
|
|
19
repopack.config.json
Normal file
19
repopack.config.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"output": {
|
||||||
|
"filePath": "repopack-output.txt",
|
||||||
|
"style": "xml",
|
||||||
|
"removeComments": false,
|
||||||
|
"removeEmptyLines": false,
|
||||||
|
"topFilesLength": 5,
|
||||||
|
"showLineNumbers": false
|
||||||
|
},
|
||||||
|
"include": [],
|
||||||
|
"ignore": {
|
||||||
|
"useGitignore": true,
|
||||||
|
"useDefaultPatterns": false,
|
||||||
|
"customPatterns": []
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"enableSecurityCheck": true
|
||||||
|
}
|
||||||
|
}
|
5
src/.gitignore
vendored
Normal file
5
src/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
1121
src/package-lock.json
generated
1121
src/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,19 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.2",
|
"bootstrap": "^5.3.3",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"lz-string": "^1.5.0"
|
"lz-string": "^1.5.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "sudo npm install -g sass",
|
"postinstall": "sudo npm install -g sass@1.77.6",
|
||||||
"build": "sass --style compressed scss/custom.scss:../dist/css/bootstrap.min.css && cp node_modules/lz-string/libs/lz-string.min.js ../dist/js/lz-string.min.js",
|
"build": "sass --style compressed scss/custom.scss:../dist/css/bootstrap.min.css && cp node_modules/lz-string/libs/lz-string.min.js ../dist/js/lz-string.min.js",
|
||||||
"setup:certs": "mkdir -p certs; mkcert -cert-file certs/cert.pem -key-file certs/cert.key localhost 127.0.0.1",
|
"test": "npx playwright test",
|
||||||
"start": "node node_modules/http-server/bin/http-server ../dist -c-1",
|
"setup:certs": "mkdir -p certs; mkcert -cert-file certs/cert.pem -key-file certs/cert.key localhost 127.0.0.1",
|
||||||
"local-secure-start": "node node_modules/http-server/bin/http-server ../dist -c-1 -C certs/cert.pem -K certs/cert.key -S -p 8443"
|
"start": "node node_modules/http-server/bin/http-server ../dist -c-1",
|
||||||
}
|
"local-secure-start": "node node_modules/http-server/bin/http-server ../dist -c-1 -C certs/cert.pem -K certs/cert.key -S -p 8443"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.48.0",
|
||||||
|
"@types/node": "^22.7.5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
99
src/playwright.config.ts
Normal file
99
src/playwright.config.ts
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// import dotenv from 'dotenv';
|
||||||
|
// import path from 'path';
|
||||||
|
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests',
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: true,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: 'html',
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
baseURL: 'https://localhost:8443',
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Chrome'],
|
||||||
|
permissions: ['clipboard-read', 'clipboard-write'],
|
||||||
|
viewport: {
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Firefox'],
|
||||||
|
viewport: {
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
//{
|
||||||
|
// name: 'webkit',
|
||||||
|
// use: {
|
||||||
|
// ...devices['Desktop Safari'],
|
||||||
|
// permissions: ['clipboard-read'],
|
||||||
|
// viewport: {
|
||||||
|
// width: 1920,
|
||||||
|
// height: 1080,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
//},
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run build && npm run local-secure-start',
|
||||||
|
port: 8443,
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
});
|
2
src/tests/.gitignore
vendored
Normal file
2
src/tests/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# This file contains a private dataset used for testing real world use cases
|
||||||
|
real-world-functional.spec.ts
|
192
src/tests/deep-functional.spec.ts
Normal file
192
src/tests/deep-functional.spec.ts
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
async function getClipboardText(page) {
|
||||||
|
return page.evaluate(async () => {
|
||||||
|
return await navigator.clipboard.readText();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Deep Functional Test', async ({ page }) => {
|
||||||
|
// The goal of this test is to identify any weird interdependencies or issues that may arise
|
||||||
|
// from doing a variety of actions on one page load. It's meant to emulate a complex human
|
||||||
|
// user interaction often with steps that don't make sense.
|
||||||
|
// This does a little of everything:
|
||||||
|
// - Manual Network Input
|
||||||
|
// - Subnet splitting/joining
|
||||||
|
// - Colors
|
||||||
|
// - Sharable URLs
|
||||||
|
// - AWS/Azure Mode
|
||||||
|
// - Import Reddit Example Config
|
||||||
|
// - Change Network Size
|
||||||
|
await page.goto('/');
|
||||||
|
// Change 10.0.0.0/8 -> 172.16.0.0/12
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').press('Shift+Home');
|
||||||
|
await page.getByLabel('Network Address').fill('172.16.0.0');
|
||||||
|
await page.getByLabel('Network Size').fill('12');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
// Do a bunch of splitting
|
||||||
|
await page.getByText('/12', { exact: true }).click();
|
||||||
|
await page.getByLabel('172.24.0.0/13', { exact: true }).getByText('/13', { exact: true }).click();
|
||||||
|
await page.getByLabel('172.24.0.0/14', { exact: true }).getByText('/14', { exact: true }).click();
|
||||||
|
await page.getByLabel('172.26.0.0/15', { exact: true }).getByText('/15', { exact: true }).click();
|
||||||
|
await page.getByLabel('172.26.0.0/16', { exact: true }).getByText('/16', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '/15 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '172.24.0.0/16 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '172.25.0.0/16 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '/14 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '172.30.0.0/15 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '172.31.0.0/16 Split' }).click();
|
||||||
|
await page.getByLabel('172.31.128.0/17', { exact: true }).getByText('/17', { exact: true }).click();
|
||||||
|
await page.getByLabel('172.31.192.0/18', { exact: true }).getByText('/18', { exact: true }).click();
|
||||||
|
await page.getByLabel('172.31.224.0/19', { exact: true }).getByText('/19', { exact: true }).click();
|
||||||
|
await page.getByLabel('172.31.192.0/19', { exact: true }).getByText('/19', { exact: true }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.240.0/20 Note' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.240.0/20 Note' }).fill('Test A');
|
||||||
|
await page.getByRole('textbox', { name: '172.31.224.0/20 Note' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.224.0/20 Note' }).fill('Test B');
|
||||||
|
await page.getByRole('cell', { name: '172.31.240.0/20 Split' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.240.0/21 Note' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.240.0/21 Note' }).fill('Test A - 1');
|
||||||
|
await page.getByRole('cell', { name: '172.31.248.0/21 Note' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.248.0/21 Note' }).fill('Test A - 2');
|
||||||
|
await page.getByLabel('172.31.248.0/21', { exact: true }).getByText('/21', { exact: true }).click();
|
||||||
|
await page.getByLabel('172.31.252.0/22', { exact: true }).getByText('/22', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '/21 Split' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.240.0/22 Note' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.240.0/22 Note' }).fill('Test A - 1A');
|
||||||
|
await page.getByRole('textbox', { name: '172.31.244.0/22 Note' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '172.31.244.0/22 Note' }).fill('Test A - 1B');
|
||||||
|
// Join a subnet
|
||||||
|
await page.getByLabel('172.31.240.0/21 Join').click();
|
||||||
|
// Change some colors and do some more splitting
|
||||||
|
await page.getByText('Change Colors »').click();
|
||||||
|
await page.getByLabel('Color 4').click();
|
||||||
|
await page.getByRole('cell', { name: '172.26.128.0/17 Usable IPs' }).click();
|
||||||
|
await page.getByText('« Stop Changing Colors').click();
|
||||||
|
await page.getByRole('cell', { name: '172.26.128.0/17 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '172.26.192.0/18 Split' }).click();
|
||||||
|
await page.getByText('Change Colors »').click();
|
||||||
|
await page.getByLabel('Color 8').click();
|
||||||
|
await page.getByRole('cell', { name: '172.26.128.0/18 Usable IPs' }).click();
|
||||||
|
await page.getByText('« Stop Changing Colors').click();
|
||||||
|
// Make sure we're still not changing colors
|
||||||
|
await page.getByRole('cell', { name: '172.26.128.0/18 Split' }).click();
|
||||||
|
// Check a bunch of specific items
|
||||||
|
await expect(page.getByLabel('Network Address')).toHaveValue('172.16.0.0');
|
||||||
|
await expect(page.getByLabel('Network Size')).toHaveValue('12');
|
||||||
|
await expect(page.getByRole('textbox', { name: '172.31.254.0/23 Note' })).toHaveValue('Test A - 2');
|
||||||
|
await expect(page.getByRole('textbox', { name: '172.31.252.0/23 Note' })).toHaveValue('Test A - 2');
|
||||||
|
await expect(page.getByRole('textbox', { name: '/22 Note' })).toHaveValue('Test A - 2');
|
||||||
|
await expect(page.getByRole('textbox', { name: '/21 Note' })).toBeEmpty();
|
||||||
|
await expect(page.getByRole('textbox', { name: '172.31.224.0/20 Note' })).toHaveValue('Test B');
|
||||||
|
await expect(page.getByLabel('172.16.0.0/13', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/13');
|
||||||
|
await expect(page.getByLabel('172.24.0.0/17', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/17');
|
||||||
|
await expect(page.getByLabel('172.26.128.0/19', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/19');
|
||||||
|
await expect(page.getByLabel('172.27.0.0/16', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/16');
|
||||||
|
await expect(page.getByLabel('172.28.0.0/15', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/15');
|
||||||
|
await expect(page.getByLabel('172.30.0.0/16', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/16');
|
||||||
|
await expect(page.getByLabel('172.31.0.0/17', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/17');
|
||||||
|
await expect(page.getByLabel('172.31.128.0/18', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/18');
|
||||||
|
await expect(page.getByLabel('172.31.192.0/20', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/20');
|
||||||
|
await expect(page.getByLabel('172.31.240.0/21', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/21');
|
||||||
|
await expect(page.getByLabel('172.31.248.0/22', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/22');
|
||||||
|
await expect(page.getByLabel('172.31.252.0/23', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/23');
|
||||||
|
await expect(page.getByLabel('/12 Join')).toContainText('/12');
|
||||||
|
await expect(page.getByLabel('/13 Join')).toContainText('/13');
|
||||||
|
await expect(page.getByLabel('172.26.128.0/17 Join')).toContainText('/17');
|
||||||
|
await expect(page.getByLabel('172.31.128.0/17 Join')).toContainText('/17');
|
||||||
|
await expect(page.getByLabel('172.31.192.0/19 Join')).toContainText('/19');
|
||||||
|
await expect(page.getByLabel('172.31.224.0/19 Join')).toContainText('/19');
|
||||||
|
await expect(page.getByLabel('/21 Join')).toContainText('/21');
|
||||||
|
await expect(page.getByLabel('/22 Join')).toContainText('/22');
|
||||||
|
await expect(page.getByRole('row', { name: '172.26.128.0/19' })).toHaveCSS('background-color', 'rgb(255, 198, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '172.26.160.0/19' })).toHaveCSS('background-color', 'rgb(255, 198, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '172.26.192.0/19' })).toHaveCSS('background-color', 'rgb(202, 255, 191)');
|
||||||
|
await expect(page.getByRole('row', { name: '172.26.224.0/19' })).toHaveCSS('background-color', 'rgb(202, 255, 191)');
|
||||||
|
// Check the Shareable URL
|
||||||
|
await page.getByText('Copy Shareable URL').click();
|
||||||
|
let clipboardUrl = await getClipboardText(page);
|
||||||
|
expect(clipboardUrl).toContain('/index.html?c=1N4IgbiBcIEwgNCARlEBGA7DAdGgbNgAxED0aciAzlKIQMY0iEAmNAvomq5KDAKaMALADNGADgDmjfAAt2nDHJ5sOIAJxSe6MUuCq0a3StUBWUVrSFNvQkcQw0ukIJgBLcYIBWjBtADEwsJ0eIEgqmIm3lq+IAFBIaIqiIIAzO5aYnhRoDF+dACGgUiJiGIY2SC5BUWJxpxo1nUgKQJaIfIgGOagaIKNnCbWzbYdKY6MeG4deGnSMFmMMCYwANYdSylryvow5YsmglugAHaoACp8lAAuAAQAQmH2JiZHICaWADaMp9AIlaiPN5oNBfCyEGAwAC233Ol1uAEEbgBaG5wfTglLQrQwQiCPA-E6w643REotH2XEYAkgH4gC7E0mosLGFmsthAA');
|
||||||
|
// Check the Export
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Import / Export' }).click();
|
||||||
|
await expect(page.getByLabel('Import/Export Content')).toHaveValue('{\n "config_version": "2",\n "base_network": "172.16.0.0/12",\n "subnets": {\n "172.16.0.0/12": {\n "172.16.0.0/13": {},\n "172.24.0.0/13": {\n "172.24.0.0/14": {\n "172.24.0.0/15": {\n "172.24.0.0/16": {\n "172.24.0.0/17": {},\n "172.24.128.0/17": {}\n },\n "172.25.0.0/16": {\n "172.25.0.0/17": {},\n "172.25.128.0/17": {}\n }\n },\n "172.26.0.0/15": {\n "172.26.0.0/16": {\n "172.26.0.0/17": {},\n "172.26.128.0/17": {\n "172.26.128.0/18": {\n "172.26.128.0/19": {\n "_color": "#ffc6ff"\n },\n "172.26.160.0/19": {\n "_color": "#ffc6ff"\n }\n },\n "172.26.192.0/18": {\n "172.26.192.0/19": {\n "_color": "#caffbf"\n },\n "172.26.224.0/19": {\n "_color": "#caffbf"\n }\n }\n }\n },\n "172.27.0.0/16": {}\n }\n },\n "172.28.0.0/14": {\n "172.28.0.0/15": {},\n "172.30.0.0/15": {\n "172.30.0.0/16": {},\n "172.31.0.0/16": {\n "172.31.0.0/17": {},\n "172.31.128.0/17": {\n "172.31.128.0/18": {},\n "172.31.192.0/18": {\n "172.31.192.0/19": {\n "172.31.192.0/20": {},\n "172.31.208.0/20": {}\n },\n "172.31.224.0/19": {\n "172.31.224.0/20": {\n "_note": "Test B"\n },\n "172.31.240.0/20": {\n "172.31.240.0/21": {\n "_note": "",\n "_color": ""\n },\n "172.31.248.0/21": {\n "172.31.248.0/22": {\n "_note": "Test A - 2"\n },\n "172.31.252.0/22": {\n "172.31.252.0/23": {\n "_note": "Test A - 2"\n },\n "172.31.254.0/23": {\n "_note": "Test A - 2"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}');
|
||||||
|
await page.getByLabel('Import/Export', { exact: true }).getByText('Close').click();
|
||||||
|
// Set to AWS Mode
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - AWS' }).click();
|
||||||
|
// Check AWS Mode Settings
|
||||||
|
await expect(page.getByLabel('172.31.254.0/23', { exact: true }).getByLabel('Usable IPs (AWS)')).toContainText('172.31.254.4 - 172.31.255.254');
|
||||||
|
await expect(page.getByLabel('172.31.254.0/23', { exact: true }).getByLabel('Hosts')).toContainText('507');
|
||||||
|
await page.getByText('Copy Shareable URL').click();
|
||||||
|
clipboardUrl = await getClipboardText(page);
|
||||||
|
expect(clipboardUrl).toContain('/index.html?c=1N4IgbiBcIEwgNCARlEBGA7DAdGgbNgAxED0aciAtqgIIDqAygiAM5SiEDG7IhAJuwC+iNAMigYAUx4AWAGY8AHAHMe+ABZCRGTeMHCQATlXj0i3cANpDF-QYCsC02kImJhW4hhoLIGTABLJRkAKx5uaABiOTlOPBiQA0V7MNMIkGjY+IV9RBkAZiDTRTxU0HTIzgBDGKQcxEUMMpAK6tqcuxE0N06QfOlTeK0QDCdQNBkekXs3Po9h-J8ePEDhvEK1GFKeGHsYAGth3fzDvSsYJp37GVPQADtUABVJFgAXAAIAIUSve3tbkD2FwAGx4D2gzHSP0BaDQoOchBgMGopnBIGeb3eNHeAFp3nArIj8ij3DI8OD7k8Xh9sXiCV5CDIMBSQGiMTTcfjEnYebzBEA');
|
||||||
|
// Set to Azure Mode
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - Azure' }).click();
|
||||||
|
// Check Azure Mode Settings
|
||||||
|
await expect(page.getByLabel('172.31.254.0/23', { exact: true }).getByLabel('Usable IPs (Azure)')).toContainText('172.31.254.4 - 172.31.255.254');
|
||||||
|
await expect(page.getByLabel('172.31.254.0/23', { exact: true }).getByLabel('Hosts')).toContainText('507');
|
||||||
|
await page.getByText('Copy Shareable URL').click();
|
||||||
|
clipboardUrl = await getClipboardText(page);
|
||||||
|
expect(clipboardUrl).toContain('/index.html?c=1N4IgbiBcIEwgNCARlEBGA7DAdGgbNgAxED0aciAtqgIIBaAqgEoCiCIAzlKIQMbchCAE24BfRGhGRQMAKYCALADMBADgDmA-AAsxEjLumjxIAJybp6VYeAm0pm8ZMBWFZbSELMwo8Qw0NiAKMACWagoAVgL80ADESkq8eAkgJqrOUZYxIPGJySrGiAoAzGGWqniZoNmxvACGCUgFiKoYVSA19Y0FThJoXr0gxfKWyXogGG6gaAoDEs5eQz7jxQECeKHjeKVaMJUCMM4wANbjh8WnRnYwbQfOCpegAHaoACqyHAAuAAQAQql+ZzOR4gZweAA2Ahe0HY2QBoLQaEh7kIMBg1Es0JA7y+3xo3wAtN84HZUcUMd4FHhoc83h8fviiSS-IQFBgaSAsTiGYTiaknALBaIgA');
|
||||||
|
// Import Default Reddit Config
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Import / Export' }).click();
|
||||||
|
await page.getByLabel('Import/Export Content').click();
|
||||||
|
await page.getByLabel('Import/Export Content').press('ControlOrMeta+a');
|
||||||
|
await page.getByLabel('Import/Export Content').fill('{\n "config_version": "2",\n "base_network": "10.0.0.0/20",\n "subnets": {\n "10.0.0.0/20": {\n "10.0.0.0/21": {\n "10.0.0.0/22": {\n "10.0.0.0/23": {\n "10.0.0.0/24": {\n "_note": "Data Center - Virtual Servers",\n "_color": "#9bf6ff"\n },\n "10.0.1.0/24": {\n "_note": "Data Center - Virtual Servers",\n "_color": "#9bf6ff"\n }\n },\n "10.0.2.0/23": {\n "10.0.2.0/24": {\n "_note": "Data Center - Virtual Servers",\n "_color": "#9bf6ff"\n },\n "10.0.3.0/24": {\n "_note": "Data Center - Physical Servers",\n "_color": "#a0c4ff"\n }\n }\n },\n "10.0.4.0/22": {\n "10.0.4.0/23": {\n "_note": "Building A - Wifi",\n "_color": "#ffd6a5"\n },\n "10.0.6.0/23": {\n "_note": "Building A - LAN",\n "_color": "#ffd6a5"\n }\n }\n },\n "10.0.8.0/21": {\n "10.0.8.0/22": {\n "10.0.8.0/23": {\n "10.0.8.0/24": {\n "_note": "Building A - Printers",\n "_color": "#ffd6a5"\n },\n "10.0.9.0/24": {\n "_note": "Building A - Voice",\n "_color": "#ffd6a5"\n }\n },\n "10.0.10.0/23": {\n "_note": "Building B - Wifi",\n "_color": "#fdffb6"\n }\n },\n "10.0.12.0/22": {\n "10.0.12.0/23": {\n "_note": "Building B - LAN",\n "_color": "#fdffb6"\n },\n "10.0.14.0/23": {\n "10.0.14.0/24": {\n "_note": "Building B - Printers",\n "_color": "#fdffb6"\n },\n "10.0.15.0/24": {\n "_note": "Building B - Voice",\n "_color": "#fdffb6"\n }\n }\n }\n }\n }\n }\n}');
|
||||||
|
await page.getByRole('button', { name: 'Import' }).click();
|
||||||
|
// Do all the Reddit Default Checks
|
||||||
|
await expect(page.getByLabel('Network Address')).toHaveValue('10.0.0.0');
|
||||||
|
await expect(page.getByLabel('Network Size')).toHaveValue('20');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.0.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.1.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.1.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.2.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.2.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.3.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.3.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.4.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.4.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.6.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.6.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.8.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.8.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.9.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.9.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.10.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.10.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.12.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.12.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.14.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.14.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.15.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.15.0/24');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.0.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.1.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.2.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.3.0/24 Note' })).toHaveValue('Data Center - Physical Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.4.0/23 Note' })).toHaveValue('Building A - Wifi');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.6.0/23 Note' })).toHaveValue('Building A - LAN');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.8.0/24 Note' })).toHaveValue('Building A - Printers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.9.0/24 Note' })).toHaveValue('Building A - Voice');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.10.0/23 Note' })).toHaveValue('Building B - Wifi');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.12.0/23 Note' })).toHaveValue('Building B - LAN');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.14.0/24 Note' })).toHaveValue('Building B - Printers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.15.0/24 Note' })).toHaveValue('Building B - Voice');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.1.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.2.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.3.0/24' })).toHaveCSS('background-color', 'rgb(160, 196, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.4.0/23' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.6.0/23' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.8.0/24' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.9.0/24' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.10.0/23' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.12.0/23' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.14.0/24' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.15.0/24' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
// Now change the whole network address
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').press('ControlOrMeta+a');
|
||||||
|
await page.getByLabel('Network Address').fill('192.168.0.0');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.getByLabel('192.168.0.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('192.168.0.0/24');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//test('Test', async ({ page }) => {
|
||||||
|
// await page.goto('/');
|
||||||
|
//});
|
22
src/tests/default-homepage.spec.ts
Normal file
22
src/tests/default-homepage.spec.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('Default Homepage Rendering', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page).toHaveTitle(/Visual Subnet Calculator/);
|
||||||
|
await expect(page.getByRole('heading')).toContainText('Visual Subnet Calculator');
|
||||||
|
await expect(page.getByLabel('Network Address')).toHaveValue('10.0.0.0');
|
||||||
|
await expect(page.getByLabel('Network Size')).toHaveValue('16');
|
||||||
|
await expect(page.locator('#useableHeader')).toContainText('Usable IPs');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.0.0/16');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Range of Addresses')).toContainText('10.0.0.0 - 10.0.255.255');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Usable IPs')).toContainText('10.0.0.1 - 10.0.255.254');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Hosts')).toContainText('65534');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.0.0/16 Note' })).toBeEmpty();
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/16');
|
||||||
|
// This "default no color" check could maybe be improved. May not be reliable cross-browser.
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/16' })).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||||||
|
await expect(page.getByLabel('Change Colors').locator('span')).toContainText('Change Colors »');
|
||||||
|
await expect(page.locator('#copy_url')).toContainText('Copy Shareable URL');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
47
src/tests/import-export.spec.ts
Normal file
47
src/tests/import-export.spec.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('Default Export Content', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Import / Export' }).click();
|
||||||
|
await expect(page.locator('#importExportModalLabel')).toContainText('Import/Export');
|
||||||
|
await expect(page.getByLabel('Import/Export', { exact: true })).toContainText('Close');
|
||||||
|
await expect(page.locator('#importBtn')).toContainText('Import');
|
||||||
|
await expect(page.getByLabel('Import/Export Content')).toHaveValue('{\n "config_version": "2",\n "base_network": "10.0.0.0/16",\n "subnets": {\n "10.0.0.0/16": {}\n }\n}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Default (AWS) Export Content', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - AWS' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Import / Export' }).click();
|
||||||
|
await expect(page.getByLabel('Import/Export Content')).toHaveValue('{\n "config_version": "2",\n "operating_mode": "AWS",\n "base_network": "10.0.0.0/16",\n "subnets": {\n "10.0.0.0/16": {}\n }\n}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Default (Azure) Export Content', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - Azure' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Import / Export' }).click();
|
||||||
|
await expect(page.getByLabel('Import/Export Content')).toHaveValue('{\n "config_version": "2",\n "operating_mode": "AZURE",\n "base_network": "10.0.0.0/16",\n "subnets": {\n "10.0.0.0/16": {}\n }\n}');
|
||||||
|
await page.getByLabel('Import/Export', { exact: true }).getByText('Close').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Import 192.168.0.0/24', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Import / Export' }).click();
|
||||||
|
await page.getByLabel('Import/Export Content').click();
|
||||||
|
await page.getByLabel('Import/Export Content').fill('{\n "config_version": "2",\n "base_network": "192.168.0.0/24",\n "subnets": {\n "192.168.0.0/24": {}\n }\n}');
|
||||||
|
await page.getByRole('button', { name: 'Import' }).click();
|
||||||
|
await expect(page.getByLabel('Network Address')).toHaveValue('192.168.0.0');
|
||||||
|
await expect(page.getByLabel('Network Size')).toHaveValue('24');
|
||||||
|
await expect(page.getByLabel('192.168.0.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('192.168.0.0/24');
|
||||||
|
});
|
||||||
|
|
||||||
|
//test('Test', async ({ page }) => {
|
||||||
|
// await page.goto('/');
|
||||||
|
//});
|
||||||
|
|
BIN
src/tests/real-world-functional.spec.ts.gpg
Normal file
BIN
src/tests/real-world-functional.spec.ts.gpg
Normal file
Binary file not shown.
174
src/tests/subnet-basic.spec.ts
Normal file
174
src/tests/subnet-basic.spec.ts
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
async function getClipboardText(page) {
|
||||||
|
return page.evaluate(async () => {
|
||||||
|
return await navigator.clipboard.readText();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Renders Max Depth /0 to /32', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').press('Shift+Home');
|
||||||
|
await page.getByLabel('Network Address').fill('0.0.0.0');
|
||||||
|
await page.getByLabel('Network Address').press('Tab');
|
||||||
|
await page.getByLabel('Network Size').fill('0');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/0 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/1 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/2 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/3 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/4 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/5 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/6 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/7 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/8 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/9 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/10 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/11 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/12 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/13 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/14 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/15 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/16 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/17 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/18 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/19 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/20 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/21 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/22 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/23 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/24 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/25 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/26 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/27 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/28 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/29 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/30 Split' }).click();
|
||||||
|
await page.getByRole('cell', { name: '0.0.0.0/31 Split' }).click();
|
||||||
|
await expect(page.getByLabel('0.0.0.0/32', { exact: true }).getByLabel('Subnet Address')).toContainText('0.0.0.0/32');
|
||||||
|
await expect(page.getByLabel('0.0.0.0/32', { exact: true }).getByLabel('Range of Addresses')).toContainText('0.0.0.0');
|
||||||
|
await expect(page.getByLabel('0.0.0.0/32', { exact: true }).getByLabel('Usable IPs')).toContainText('0.0.0.0');
|
||||||
|
await expect(page.getByLabel('0.0.0.0/32', { exact: true }).getByLabel('Hosts')).toContainText('1');
|
||||||
|
await expect(page.getByLabel('0.0.0.0/32', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/32');
|
||||||
|
await expect(page.getByLabel('/31 Join')).toContainText('/31');
|
||||||
|
await expect(page.getByLabel('128.0.0.0/1', { exact: true }).getByLabel('Subnet Address')).toContainText('128.0.0.0/1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Change To 192.168.0.0/24', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').fill('192.168.0.0');
|
||||||
|
await page.getByLabel('Network Size').click();
|
||||||
|
await page.getByLabel('Network Size').fill('24');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.getByLabel('192.168.0.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('192.168.0.0/24');
|
||||||
|
await expect(page.getByLabel('192.168.0.0/24', { exact: true }).getByLabel('Range of Addresses')).toContainText('192.168.0.0 - 192.168.0.255');
|
||||||
|
await expect(page.getByLabel('192.168.0.0/24', { exact: true }).getByLabel('Usable IPs')).toContainText('192.168.0.1 - 192.168.0.254');
|
||||||
|
await expect(page.getByLabel('192.168.0.0/24', { exact: true }).getByLabel('Hosts')).toContainText('254');
|
||||||
|
await expect(page.getByLabel('192.168.0.0/24', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/24');
|
||||||
|
await expect(page.getByRole('textbox', { name: '192.168.0.0/24 Note' })).toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Deep /32 Split', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByText('/16', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.128.0/17', { exact: true }).getByText('/17', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.128.0/18', { exact: true }).getByText('/18', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.160.0/19', { exact: true }).getByText('/19', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/20', { exact: true }).getByText('/20', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.184.0/21', { exact: true }).getByText('/21', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/21', { exact: true }).getByText('/21', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/22', { exact: true }).getByText('/22', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/23', { exact: true }).getByText('/23', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/24', { exact: true }).getByText('/24', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/25', { exact: true }).getByText('/25', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/26', { exact: true }).getByText('/26', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/27', { exact: true }).getByText('/27', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/28', { exact: true }).getByText('/28', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/29', { exact: true }).getByText('/29', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/30', { exact: true }).getByText('/30', { exact: true }).click();
|
||||||
|
await page.getByLabel('10.0.176.0/31', { exact: true }).getByText('/31', { exact: true }).click();
|
||||||
|
await page.getByRole('textbox', { name: '10.0.176.0/32 Note' }).click();
|
||||||
|
await page.getByRole('textbox', { name: '10.0.176.0/32 Note' }).fill('Test Text');
|
||||||
|
await page.getByText('Change Colors »').click();
|
||||||
|
await page.locator('#palette_picker_6').click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.176.0/32 Subnet Address' }).click();
|
||||||
|
await page.getByText('« Stop Changing Colors').click();
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').fill('99.0.0.0');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.getByLabel('99.0.176.0/32', { exact: true }).getByLabel('Subnet Address')).toContainText('99.0.176.0/32');
|
||||||
|
await expect(page.getByLabel('99.0.176.0/32', { exact: true }).getByLabel('Hosts')).toContainText('1');
|
||||||
|
await expect(page.getByRole('textbox', { name: '99.0.176.0/32 Note' })).toHaveValue('Test Text');
|
||||||
|
await expect(page.getByLabel('99.0.176.0/32', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/32');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Usable IPs - Standard', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - Standard' }).click();
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Usable IPs')).toContainText('10.0.0.1 - 10.0.255.254');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Usable IPs - AWS', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - AWS' }).click();
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Usable IPs')).toContainText('10.0.0.4 - 10.0.255.254');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Usable IPs - Azure', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - Azure' }).click();
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Usable IPs')).toContainText('10.0.0.4 - 10.0.255.254');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Note Splitting', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Note Split/Join').click();
|
||||||
|
await page.getByLabel('Note Split/Join').fill('This should be duplicated!');
|
||||||
|
await page.getByRole('cell', { name: '/16 Split' }).click();
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.0.0/17 Note' })).toHaveValue('This should be duplicated!');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.128.0/17 Note' })).toHaveValue('This should be duplicated!');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Note Joining Same', async ({ page }) => {
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9GgGwIgDOUoGA5hSBgBa0B2qAKvQJakAEp9A9gFcANgBNuSAKbdRggA7COAYwCGAF0miAhCAC+iNI0igW0dl14CR4qTPmLVG7Xt2ugA');
|
||||||
|
await page.getByLabel('/16 Join').click();
|
||||||
|
await expect(page.getByLabel('Note Split/Join')).toHaveValue('This should be duplicated!');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Note Joining Different', async ({ page }) => {
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9GgGwIgDOUoGA5hSBgBa0B2qAKvQJakAEp9A9gFcANgBNuSAKbdRggA7COAYwCGAF0miAhNwCCIAL6I0jSKBbR2XXgJHipM+YtUbt3AEKGD3oA');
|
||||||
|
await page.getByLabel('/16 Join').click();
|
||||||
|
await expect(page.getByLabel('Note Split/Join')).toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Color Splitting', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByText('Change Colors »').click();
|
||||||
|
await page.getByLabel('Color 5').click();
|
||||||
|
await page.getByRole('cell', { name: '/16 Subnet Address' }).click();
|
||||||
|
await page.getByText('« Stop Changing Colors').click();
|
||||||
|
await page.getByText('/16', { exact: true }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/17' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.128.0/17' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Color Joining Same', async ({ page }) => {
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9GgGwIgDOUoGA5hSBgBa0DGqAxAJxIBmhXXIAX0RpGkUC2gduvfgLkCgA');
|
||||||
|
await page.getByLabel('/16 Join').click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/16' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Color Joining Different', async ({ page }) => {
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9GgGwIgDOUoGA5hSBgBa0DGqAxAJxIBmhXXIAX0RpGkUC2isuAEz5JiAxQKA');
|
||||||
|
await page.getByLabel('/16 Join').click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/16' })).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||||||
|
});
|
||||||
|
|
||||||
|
//test('Test', async ({ page }) => {
|
||||||
|
// await page.goto('/');
|
||||||
|
//});
|
76
src/tests/ui-error-handling.spec.ts
Normal file
76
src/tests/ui-error-handling.spec.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('Bad Network Address', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').fill('1');
|
||||||
|
await page.locator('html').click();
|
||||||
|
await expect(page.locator('#network')).toHaveClass(/error/i);
|
||||||
|
await expect(page.getByText('Must be a valid IPv4 Address')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Bad Network Size', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Network Size').click();
|
||||||
|
await page.getByLabel('Network Size').fill('33');
|
||||||
|
await page.locator('html').click();
|
||||||
|
await expect(page.locator('#netsize')).toHaveClass(/error/i);
|
||||||
|
await expect(page.getByText('Smallest size is /32')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Prevent Go on Bad Input', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Network Size').click();
|
||||||
|
await page.getByLabel('Network Size').fill('33');
|
||||||
|
await page.locator('html').click();
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.locator('#notifyModalLabel')).toContainText('Warning!');
|
||||||
|
await expect(page.locator('#notifyModalDescription')).toContainText('Please correct the errors in the form!');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('Network Boundary Correction', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').fill('123.45.67.89');
|
||||||
|
await page.getByLabel('Network Size').click();
|
||||||
|
await page.getByLabel('Network Size').fill('20');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.locator('#notifyModalLabel')).toContainText('Warning!');
|
||||||
|
await expect(page.locator('#notifyModalDescription')).toContainText('Your network input is not on a network boundary for this network size. It has been automatically changed:');
|
||||||
|
await expect(page.locator('#notifyModalDescription')).toContainText('123.45.67.89 -> 123.45.64.0');
|
||||||
|
await page.getByLabel('Warning!').getByLabel('Close').click();
|
||||||
|
await expect(page.getByLabel('Network Address')).toHaveValue('123.45.64.0');
|
||||||
|
await page.getByLabel('Network Size').click();
|
||||||
|
await expect(page.getByRole('cell', { name: '123.45.64.0/20 Subnet Address' })).toContainText('123.45.64.0/20');
|
||||||
|
await page.getByRole('cell', { name: '/20 Split' }).click();
|
||||||
|
await page.getByLabel('/20 Join').click();
|
||||||
|
await expect(page.getByLabel('123.45.64.0/20', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/20');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Subnet Too Small for AWS Mode', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('#useableHeader')).toContainText('Usable IPs');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - AWS' }).click();
|
||||||
|
await page.getByLabel('Network Size').click();
|
||||||
|
await page.getByLabel('Network Size').fill('29');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.locator('#notifyModalLabel')).toContainText('Warning!');
|
||||||
|
await expect(page.locator('#notifyModalDescription')).toContainText('Please correct the errors in the form!');
|
||||||
|
await expect(page.getByText('AWS Mode - Smallest size is /28')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Subnet Too Small for Azure Mode', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('#useableHeader')).toContainText('Usable IPs');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - Azure' }).click();
|
||||||
|
await page.getByLabel('Network Size').click();
|
||||||
|
await page.getByLabel('Network Size').fill('30');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.locator('#notifyModalLabel')).toContainText('Warning!');
|
||||||
|
await expect(page.locator('#notifyModalDescription')).toContainText('Please correct the errors in the form!');
|
||||||
|
await expect(page.getByText('Azure Mode - Smallest size is /29')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
168
src/tests/ui-usage.spec.ts
Normal file
168
src/tests/ui-usage.spec.ts
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('CIDR Input Typing', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').press('End');
|
||||||
|
await page.getByLabel('Network Address').press('Shift+Home');
|
||||||
|
await page.getByLabel('Network Address').press('Delete');
|
||||||
|
await page.keyboard.type('192.168.0.0/24');
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.getByRole('cell', { name: '192.168.0.0/24 Subnet Address' })).toContainText('192.168.0.0/24');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CIDR Input Paste', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByLabel('Network Address').click();
|
||||||
|
await page.getByLabel('Network Address').press('End');
|
||||||
|
await page.getByLabel('Network Address').press('Shift+Home');
|
||||||
|
// From: https://github.com/microsoft/playwright/issues/2511
|
||||||
|
await page.locator('#network').evaluate((formEl) => {
|
||||||
|
const data = `172.16.0.0/12`;
|
||||||
|
const clipboardData = new DataTransfer();
|
||||||
|
const dataType = 'text/plain';
|
||||||
|
clipboardData.setData(dataType, data);
|
||||||
|
const clipboardEvent = new ClipboardEvent('paste', {
|
||||||
|
clipboardData,
|
||||||
|
dataType,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
formEl.dispatchEvent(clipboardEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click();
|
||||||
|
await expect(page.getByRole('cell', { name: '172.16.0.0/12 Subnet Address' })).toContainText('172.16.0.0/12');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('About Dialog', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.locator('#info_icon').click();
|
||||||
|
await expect(page.locator('#aboutModalLabel')).toContainText('About Visual Subnet Calculator');
|
||||||
|
await expect(page.getByLabel('About Visual Subnet Calculator')).toContainText('Design Tenets');
|
||||||
|
await expect(page.getByLabel('About Visual Subnet Calculator')).toContainText('Credits');
|
||||||
|
await expect(page.getByLabel('About Visual Subnet Calculator').getByText('Close')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GitHub Link', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
const page1Promise = page.waitForEvent('popup');
|
||||||
|
await page.getByLabel('GitHub').click();
|
||||||
|
const page1 = await page1Promise;
|
||||||
|
await expect(page1.locator('#repository-container-header')).toContainText('ckabalan / visualsubnetcalc Public');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Table Header Standard Mode', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('#useableHeader')).toContainText('Usable IPs');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Table Header AWS Mode', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('#useableHeader')).toContainText('Usable IPs');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - AWS' }).click();
|
||||||
|
await expect(page.getByRole('cell', { name: 'Usable IPs', exact: true })).toContainText('Usable IPs (AWS)');
|
||||||
|
await page.getByRole('link', { name: 'AWS' }).hover()
|
||||||
|
await expect(page.getByText('AWS reserves 5 addresses in')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Table Header Azure Mode', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('#useableHeader')).toContainText('Usable IPs');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - Azure' }).click();
|
||||||
|
await expect(page.getByRole('cell', { name: 'Usable IPs', exact: true })).toContainText('Usable IPs (Azure)');
|
||||||
|
await page.getByRole('link', { name: 'Azure' }).hover()
|
||||||
|
await expect(page.getByText('Azure reserves 5 addresses in')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('Table Header AWS then Standard', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('#useableHeader')).toContainText('Usable IPs');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - AWS' }).click();
|
||||||
|
await expect(page.getByRole('cell', { name: 'Usable IPs', exact: true })).toContainText('Usable IPs (AWS)');
|
||||||
|
await page.getByRole('button', { name: 'Tools' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Mode - Standard' }).click();
|
||||||
|
await expect(page.getByRole('cell', { name: 'Usable IPs', exact: true })).toContainText('Usable IPs');
|
||||||
|
await expect(page.getByRole('cell', { name: 'Usable IPs', exact: true })).not.toContainText('(AWS)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Color Palette', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByLabel('Change Colors').locator('span')).toContainText('Change Colors »');
|
||||||
|
await page.getByText('Change Colors »').click();
|
||||||
|
await expect(page.getByLabel('Color 1', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 2', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 3', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 4', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 5', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 6', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 7', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 8', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 9', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Color 10', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Stop Changing Colors').locator('span')).toContainText('« Stop Changing Colors');
|
||||||
|
await page.getByText('« Stop Changing Colors').click();
|
||||||
|
await expect(page.getByLabel('Change Colors').locator('span')).toContainText('Change Colors »');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test Default Colors', async ({ page }) => {
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9GgGwIgDOUoGA5hSBgBa0YCWTAVkwNYUC+ia3SMB590HIbEHDEAZikjRaVhJjjQAFnmIArPNEy1IQlpAB2PSP6MVyjYYAcJgJx6dhzCbQDelkDNtG7jCecj6Ipu6avPy6PgoiQA');
|
||||||
|
await page.getByText('Change Colors »').click();
|
||||||
|
// Set the top 10 rows to the default colors and check that they are correct
|
||||||
|
await page.getByLabel('Color 1', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.0.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/20' })).toHaveCSS('background-color', 'rgb(255, 173, 173)');
|
||||||
|
await page.getByLabel('Color 2', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.16.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.16.0/20' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await page.getByLabel('Color 3', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.32.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.32.0/20' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await page.getByLabel('Color 4', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.48.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.48.0/20' })).toHaveCSS('background-color', 'rgb(202, 255, 191)');
|
||||||
|
await page.getByLabel('Color 5', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.64.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.64.0/20' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await page.getByLabel('Color 6', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.80.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.80.0/20' })).toHaveCSS('background-color', 'rgb(160, 196, 255)');
|
||||||
|
await page.getByLabel('Color 7', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.96.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.96.0/20' })).toHaveCSS('background-color', 'rgb(189, 178, 255)');
|
||||||
|
await page.getByLabel('Color 8', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.112.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.112.0/20' })).toHaveCSS('background-color', 'rgb(255, 198, 255)');
|
||||||
|
await page.getByLabel('Color 9', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.128.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.128.0/20' })).toHaveCSS('background-color', 'rgb(230, 230, 230)');
|
||||||
|
await page.getByLabel('Color 10', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.144.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.144.0/20' })).toHaveCSS('background-color', 'rgb(255, 255, 255)');
|
||||||
|
// Set rows 11 and 12 to Colors 1 and 2 respectively and check that they are correct
|
||||||
|
await page.getByLabel('Color 1', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.160.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.160.0/20' })).toHaveCSS('background-color', 'rgb(255, 173, 173)');
|
||||||
|
await page.getByLabel('Color 2', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.176.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.176.0/20' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
// Set rows 11 and 12 to Color 10 (white) to make sure you can change colors later
|
||||||
|
await page.getByLabel('Color 10', { exact: true }).click();
|
||||||
|
await page.getByRole('cell', { name: '10.0.160.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.160.0/20' })).toHaveCSS('background-color', 'rgb(255, 255, 255)');
|
||||||
|
await page.getByRole('cell', { name: '10.0.176.0/20 Subnet Address' }).click();
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.176.0/20' })).toHaveCSS('background-color', 'rgb(255, 255, 255)');
|
||||||
|
await page.getByText('« Stop Changing Colors').click();
|
||||||
|
// Make sure when you're not in color change mode you cannot change colors
|
||||||
|
await page.getByRole('cell', { name: '10.0.0.0/20 Subnet Address' }).click();
|
||||||
|
// Should still be the old color instead of white (the last palette color selected)
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/20' })).toHaveCSS('background-color', 'rgb(255, 173, 173)');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
134
src/tests/url-sharing.spec.ts
Normal file
134
src/tests/url-sharing.spec.ts
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
async function getClipboardText(page) {
|
||||||
|
return page.evaluate(async () => {
|
||||||
|
return await navigator.clipboard.readText();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Default URL Share', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByText('Copy Shareable URL').click();
|
||||||
|
const clipboardUrl = await getClipboardText(page);
|
||||||
|
expect(clipboardUrl).toContain('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9GgGwIgDOUoGA5hQL71A');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Default URL Render', async ({ page }) => {
|
||||||
|
// This should match default-homepage.spec.ts
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9GgGwIgDOUoGA5hQL71A');
|
||||||
|
await expect(page).toHaveTitle(/Visual Subnet Calculator/);
|
||||||
|
await expect(page.getByRole('heading')).toContainText('Visual Subnet Calculator');
|
||||||
|
await expect(page.getByLabel('Network Address')).toHaveValue('10.0.0.0');
|
||||||
|
await expect(page.getByLabel('Network Size')).toHaveValue('16');
|
||||||
|
await expect(page.locator('#useableHeader')).toContainText('Usable IPs');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.0.0/16');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Range of Addresses')).toContainText('10.0.0.0 - 10.0.255.255');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Usable IPs')).toContainText('10.0.0.1 - 10.0.255.254');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Hosts')).toContainText('65534');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.0.0/16 Note' })).toBeEmpty();
|
||||||
|
await expect(page.getByLabel('10.0.0.0/16', { exact: true }).getByLabel('Split', { exact: true })).toContainText('/16');
|
||||||
|
// This "default no color" check could maybe be improved. May not be reliable cross-browser.
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/16' })).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||||||
|
await expect(page.getByLabel('Change Colors').locator('span')).toContainText('Change Colors »');
|
||||||
|
await expect(page.locator('#copy_url')).toContainText('Copy Shareable URL');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Reddit Example URL Render (URL v1 - Config v1)', async ({ page }) => {
|
||||||
|
// This is great to make sure older URLs still load and render properly
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIIwgNCAzlUMAMA6LOD0AmdVWHbbAuSNUvffYjM2gZgZvPwBZiB9AOyggAIgEMALiIAEAYQCmfMbIBOkgLSSAagEslYgK4iANpIDKysMpSIeAY0EBiAJwAjAGYA2V65ABfRIywYDm4qEH5BUQkZeUUVdW1dA2MzJQslKzC7aCc3T28fPxIyfA5WUIDMEvQCENBw6EipOQVlNU0dfSNTc0sETIcXDy9ff1JmYN4BBvEmmNb1AAUACwBPJC0bLpS0jNsHEXQbTmGCworODnpy0gvq-DK6qZAAIT0tQwATLT4Ac0kAQTaAHUtK4tH09tkvB93CIAKwjIpYdylSaCV7vL6-AFtAAy-wAchCsiB7NDYQjTqMyAAODiUai0y5sJl3B5IzB0u61MJPDGfb5-QGLJTfWK7Elk1ww+GIiqOCaheovN4C7HCzQAew2smJDnJsoK1MCLDR0H5WL+z2BoPB1kl0q8zncvjOpBgVQIV0ZgU99zNKsxgsk1vU+KJ9v1HydLrdZBgtwI7IqCcVj3RqstIbaC1FLXSeqh0dczrl7rhad5GaD2NDWp1hdJjpLsdOpyAA');
|
||||||
|
await expect(page.getByLabel('Network Address')).toHaveValue('10.0.0.0');
|
||||||
|
await expect(page.getByLabel('Network Size')).toHaveValue('20');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.0.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.1.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.1.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.2.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.2.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.3.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.3.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.4.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.4.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.6.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.6.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.8.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.8.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.9.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.9.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.10.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.10.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.12.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.12.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.14.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.14.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.15.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.15.0/24');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.0.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.1.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.2.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.3.0/24 Note' })).toHaveValue('Data Center - Physical Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.4.0/23 Note' })).toHaveValue('Building A - Wifi');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.6.0/23 Note' })).toHaveValue('Building A - LAN');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.8.0/24 Note' })).toHaveValue('Building A - Printers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.9.0/24 Note' })).toHaveValue('Building A - Voice');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.10.0/23 Note' })).toHaveValue('Building B - Wifi');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.12.0/23 Note' })).toHaveValue('Building B - LAN');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.14.0/24 Note' })).toHaveValue('Building B - Printers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.15.0/24 Note' })).toHaveValue('Building B - Voice');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.1.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.2.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.3.0/24' })).toHaveCSS('background-color', 'rgb(160, 196, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.4.0/23' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.6.0/23' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.8.0/24' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.9.0/24' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.10.0/23' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.12.0/23' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.14.0/24' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.15.0/24' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Reddit Example URL Conversion (URL v1 - Config v1 to v2)', async ({ page }) => {
|
||||||
|
// Basically if a user loads a URL (say v1), we load it, but then only produce the latest version URL (v2) when they copy the URL
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIIwgNCAzlUMAMA6LOD0AmdVWHbbAuSNUvffYjM2gZgZvPwBZiB9AOyggAIgEMALiIAEAYQCmfMbIBOkgLSSAagEslYgK4iANpIDKysMpSIeAY0EBiAJwAjAGYA2V65ABfRIywYDm4qEH5BUQkZeUUVdW1dA2MzJQslKzC7aCc3T28fPxIyfA5WUIDMEvQCENBw6EipOQVlNU0dfSNTc0sETIcXDy9ff1JmYN4BBvEmmNb1AAUACwBPJC0bLpS0jNsHEXQbTmGCworODnpy0gvq-DK6qZAAIT0tQwATLT4Ac0kAQTaAHUtK4tH09tkvB93CIAKwjIpYdylSaCV7vL6-AFtAAy-wAchCsiB7NDYQjTqMyAAODiUai0y5sJl3B5IzB0u61MJPDGfb5-QGLJTfWK7Elk1ww+GIiqOCaheovN4C7HCzQAew2smJDnJsoK1MCLDR0H5WL+z2BoPB1kl0q8zncvjOpBgVQIV0ZgU99zNKsxgsk1vU+KJ9v1HydLrdZBgtwI7IqCcVj3RqstIbaC1FLXSeqh0dczrl7rhad5GaD2NDWp1hdJjpLsdOpyAA');
|
||||||
|
await page.getByText('Copy Shareable URL').click();
|
||||||
|
const clipboardUrl = await getClipboardText(page);
|
||||||
|
expect(clipboardUrl).toContain('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9DBgiAM5SgYDW5IGANjRgLaMB2jA9je9ACICGAF34ACAMIBTVoIkAnEQFoRANQCWswQFd+dEQGU5YOWUQBjVAGIAnEgBmANlu2QAX0RoukUDxADh4qRl5JTUNbV0DWSNZExBzaGs7R2cXN3QeUBhPb1Q-UUlpOUUVdS0dfUNjYniQRIcnV0QAZmyQHzyAwuCRAAUACwBPElVTcsjo2JqLfgxTABYG1LS0Fi9YDLbUACFNVToAE1VWAHMRAEFigHVVW1Vqyyd9+34AVkaQJo2fHb3Dk-PigAZM4AOXuCUezzeS3cDDWMFWoDmGwAHK1vrsDkdThclD1ZEcgpMHrYnq93lZ0dtMX8ccVlBwRhJwbVIeTUogXl9qb9sSItlcbnczA99k4kPZXGkmoiQPZudAflj-gKlMCwSKIWLbBL3gB2DZoOZUxU0vmq3oErrErXiyXLF4mkBK2n8+mM0zMzWs7W6pb+oA');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Reddit Example URL Render (URL v1 - Config v2)', async ({ page }) => {
|
||||||
|
// This is great to make sure older URLs still load and render properly
|
||||||
|
await page.goto('/index.html?c=1N4IgbiBcIEwgNCARlEBGADAOm7g9DBgiAM5SgYDW5IGANjRgLaMB2jA9je9ACICGAF34ACAMIBTVoIkAnEQFoRANQCWswQFd+dEQGU5YOWUQBjVAGIAnEgBmANlu2QAX0RoukUDxADh4qRl5JTUNbV0DWSNZExBzaGs7R2cXN3QeUBhPb1Q-UUlpOUUVdS0dfUNjYniQRIcnV0QAZmyQHzyAwuCRAAUACwBPElVTcsjo2JqLfgxTABYG1LS0Fi9YDLbUACFNVToAE1VWAHMRAEFigHVVW1Vqyyd9+34AVkaQJo2fHb3Dk-PigAZM4AOXuCUezzeS3cDDWMFWoDmGwAHK1vrsDkdThclD1ZEcgpMHrYnq93lZ0dtMX8ccVlBwRhJwbVIeTUogXl9qb9sSItlcbnczA99k4kPZXGkmoiQPZudAflj-gKlMCwSKIWLbBL3gB2DZoOZUxU0vmq3oErrErXiyXLF4mkBK2n8+mM0zMzWs7W6pb+oA');
|
||||||
|
await expect(page.getByLabel('Network Address')).toHaveValue('10.0.0.0');
|
||||||
|
await expect(page.getByLabel('Network Size')).toHaveValue('20');
|
||||||
|
await expect(page.getByLabel('10.0.0.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.0.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.1.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.1.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.2.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.2.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.3.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.3.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.4.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.4.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.6.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.6.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.8.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.8.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.9.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.9.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.10.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.10.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.12.0/23', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.12.0/23');
|
||||||
|
await expect(page.getByLabel('10.0.14.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.14.0/24');
|
||||||
|
await expect(page.getByLabel('10.0.15.0/24', { exact: true }).getByLabel('Subnet Address')).toContainText('10.0.15.0/24');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.0.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.1.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.2.0/24 Note' })).toHaveValue('Data Center - Virtual Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.3.0/24 Note' })).toHaveValue('Data Center - Physical Servers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.4.0/23 Note' })).toHaveValue('Building A - Wifi');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.6.0/23 Note' })).toHaveValue('Building A - LAN');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.8.0/24 Note' })).toHaveValue('Building A - Printers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.9.0/24 Note' })).toHaveValue('Building A - Voice');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.10.0/23 Note' })).toHaveValue('Building B - Wifi');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.12.0/23 Note' })).toHaveValue('Building B - LAN');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.14.0/24 Note' })).toHaveValue('Building B - Printers');
|
||||||
|
await expect(page.getByRole('textbox', { name: '10.0.15.0/24 Note' })).toHaveValue('Building B - Voice');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.0.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.1.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.2.0/24' })).toHaveCSS('background-color', 'rgb(155, 246, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.3.0/24' })).toHaveCSS('background-color', 'rgb(160, 196, 255)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.4.0/23' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.6.0/23' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.8.0/24' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.9.0/24' })).toHaveCSS('background-color', 'rgb(255, 214, 165)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.10.0/23' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.12.0/23' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.14.0/24' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
await expect(page.getByRole('row', { name: '10.0.15.0/24' })).toHaveCSS('background-color', 'rgb(253, 255, 182)');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//test('Test', async ({ page }) => {
|
||||||
|
// await page.goto('/');
|
||||||
|
//});
|
||||||
|
|
Loading…
Reference in a new issue