Created
July 31, 2023 15:55
-
-
Save Wh1terat/4e716b78278dd4dc3709a8ee949250e6 to your computer and use it in GitHub Desktop.
IPv4 IPAM Visualiser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>IPAM</title> | |
</head> | |
<body> | |
<canvas id="ipam"></canvas> | |
<div id="tooltip" style="position: absolute; display: none; background-color: #ffffff; border: 1px solid #000000; padding: 5px;"></div> | |
<script> | |
class DrawIPAM { | |
constructor(supernet, canvas) { | |
this.CONFIG = { | |
"colors": { | |
"background": '#202020', | |
"supernet": '#404040', | |
"border": '#000000', | |
"header": '#c0c0c0', | |
"legend": '#c0c0c0', | |
"xaxis": '#c0c0c0', | |
"yaxis": '#c0c0c0', | |
"desc": '#000000', | |
"cidr": [ | |
{cidr:"32",color:"#0000FF"}, | |
{cidr:"31",color:"#7F7F00"}, | |
{cidr:"30",color:"#FF963C"}, | |
{cidr:"29",color:"#B4FFFF"}, | |
{cidr:"28",color:"#00FF00"}, | |
{cidr:"27",color:"#FFFF7F"}, | |
{cidr:"26",color:"#FF00FF"}, | |
{cidr:"25",color:"#00C8B4"}, | |
{cidr:"24",color:"#FF007F"}, | |
{cidr:"23",color:"#FF7FFF"}, | |
{cidr:"22",color:"#00FF7F"}, | |
{cidr:"21",color:"#007F00"}, | |
{cidr:"20",color:"#FF7F7F"}, | |
] | |
}, | |
"fonts": { | |
"header": "bold 18pt Courier New", | |
"legend": "13pt Courier New", | |
"xaxis": "13pt Courier New", | |
"yaxis": "13pt Courier New", | |
"desc": "bold 12pt Courier New" | |
}, | |
"cellHeight": 30, | |
"cellWidth": 5, | |
"borderTop": 125, | |
"borderBottom": 25, | |
"borderLeft": 160, | |
"borderRight": 25, | |
"headerX": 15, | |
"headerY": 30, | |
"legendX": 45, | |
"legendY": 50, | |
"legendRectSize": 15, | |
}; | |
this.supernet = supernet; | |
this.prefixes = []; | |
this.canvas = canvas; | |
this.ctx = this.canvas.getContext("2d"); | |
this.prefixRects = []; | |
this.subnetBase = supernet.split("/")[0]; | |
this.prefixLength = supernet.split("/")[1]; | |
this.numSubnets = 2 ** (24 - parseInt(this.prefixLength)); | |
this.canvas.width = (256 * this.CONFIG.cellWidth) + this.CONFIG.borderLeft + this.CONFIG.borderRight; | |
this.canvas.height = (this.numSubnets * this.CONFIG.cellHeight) + this.CONFIG.borderTop + this.CONFIG.borderBottom; | |
this.ctx.fillStyle = this.CONFIG.colors.background; | |
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); | |
this.canvas.onclick = (event) => { | |
let x = event.pageX - this.canvas.offsetLeft; | |
let y = event.pageY - this.canvas.offsetTop; | |
let prefix = this.getPrefix(x, y); | |
if(prefix !== null) { | |
console.log(`${prefix.network} - ${prefix.description}`); | |
} | |
}; | |
this.canvas.onmousemove = function(event) { | |
let x = event.pageX - this.canvas.offsetLeft; | |
let y = event.pageY - this.canvas.offsetTop; | |
let prefix = this.getPrefix(x, y); | |
if(prefix !== null) { | |
document.getElementById('tooltip').style.display = 'block'; | |
document.getElementById('tooltip').style.left = event.pageX + 'px'; | |
document.getElementById('tooltip').style.top = (event.pageY + 20) + 'px'; | |
document.getElementById('tooltip').innerText = prefix.description; | |
} else { | |
document.getElementById('tooltip').style.display = 'none'; | |
} | |
}.bind(this); | |
this.canvas.onmouseleave = function(event) { | |
document.getElementById('tooltip').style.display = 'none'; | |
}.bind(this); | |
} | |
addPrefix(prefix) { | |
this.addPrefixes([prefix]); | |
} | |
addPrefixes(prefixes) { | |
this.prefixes = this.prefixes.concat(prefixes); | |
this.drawPrefixes(); | |
} | |
removePrefix(prefix) { | |
this.removePrefixes([prefix]); | |
} | |
removePrefixes(prefixes) { | |
this.prefixes = this.prefixes.filter(existingPrefix => { | |
for(let prefix of prefixes) { | |
if(typeof prefix === 'string') { | |
if(existingPrefix.network === prefix) { | |
return false; | |
} | |
} | |
else if(typeof prefix === 'object') { | |
if(existingPrefix.network === prefix.network && existingPrefix.description === prefix.description) { | |
return false; | |
} | |
} | |
} | |
return true; | |
}); | |
this.drawPrefixes(); | |
} | |
drawPrefixes() { | |
this.drawHeader(); | |
this.drawCidrChart(); | |
this.drawSupernetRect(); | |
this.drawColumnsAndLabels(); | |
this.drawRows(); | |
this.drawPrefixesRect(); | |
} | |
drawHeader() { | |
this.ctx.fillStyle = this.CONFIG.colors.header; | |
this.ctx.font = this.CONFIG.fonts.header; | |
this.ctx.fillText(`Netblock ${this.supernet}`, this.CONFIG.headerX, this.CONFIG.headerY); | |
} | |
drawCidrChart() { | |
let cidrLegendXPos = this.CONFIG.legendX; | |
this.CONFIG.colors.cidr.forEach((item) => { | |
this.ctx.fillStyle = item.color; | |
this.ctx.fillRect(cidrLegendXPos, this.CONFIG.legendY, this.CONFIG.legendRectSize, this.CONFIG.legendRectSize); | |
this.ctx.font = this.CONFIG.fonts.legend; | |
this.ctx.fillStyle = this.CONFIG.colors.legend; | |
const text = "/" + item.cidr; | |
const textHeight = parseInt(this.ctx.font.match(/\d+/), 10); | |
const textWidth = this.ctx.measureText(text).width; | |
const textX = cidrLegendXPos + this.CONFIG.legendRectSize + (textWidth/4); | |
const textY = this.CONFIG.legendY + ((this.CONFIG.legendRectSize/2) + (textHeight/4)); | |
this.ctx.fillText(text , textX, textY); | |
cidrLegendXPos += this.CONFIG.legendRectSize * 5; | |
}); | |
} | |
drawSupernetRect() { | |
const rectWidth = canvas.width - this.CONFIG.borderLeft - this.CONFIG.borderRight; | |
const rectHeight = this.numSubnets * this.CONFIG.cellHeight; | |
// Draw Supernet rect | |
this.ctx.rect(this.CONFIG.borderLeft, this.CONFIG.borderTop, rectWidth, rectHeight); | |
this.ctx.fillStyle = this.CONFIG.colors.supernet; | |
this.ctx.fill(); | |
// Draw Supernet border | |
this.ctx.lineWidth = 2; | |
this.ctx.strokeStyle = this.CONFIG.colors.border; | |
this.ctx.stroke(); | |
} | |
drawColumnsAndLabels() { | |
this.ctx.beginPath(); | |
this.ctx.save(); | |
this.ctx.setLineDash([5, 1]); | |
for (let i = 0; i < 16; i++) { | |
const lineX = ((i * 16) * this.CONFIG.cellWidth) + this.CONFIG.borderLeft; | |
const lineY = canvas.height - this.CONFIG.borderBottom; | |
this.ctx.moveTo(lineX, this.CONFIG.borderTop); | |
this.ctx.lineTo(lineX, lineY); | |
this.ctx.save(); | |
this.ctx.translate(lineX, this.CONFIG.borderTop - 5); | |
this.ctx.rotate(-0.5 * Math.PI); | |
this.ctx.fillStyle = this.CONFIG.colors.yaxis; | |
this.ctx.font = this.CONFIG.fonts.yaxis; | |
this.ctx.fillText((i*16).toString(), 0, 3); | |
this.ctx.restore(); | |
} | |
this.ctx.stroke(); | |
this.ctx.restore(); | |
} | |
drawRows() { | |
const [baseA, baseB] = this.subnetBase.split("."); | |
this.ctx.beginPath(); | |
for (let i = 0; i < this.numSubnets; i++) { | |
const lineX = (256 * this.CONFIG.cellWidth) + this.CONFIG.borderLeft; | |
const lineY = (i * this.CONFIG.cellHeight) + this.CONFIG.borderTop; | |
this.ctx.moveTo(this.CONFIG.borderLeft, lineY); | |
this.ctx.lineTo(lineX, lineY); | |
this.ctx.font = this.CONFIG.fonts.xaxis; | |
this.ctx.fillStyle = this.CONFIG.colors.xaxis; | |
const subnetAddress = `${baseA}.${baseB}.${i}.0`; | |
const textHeight = parseInt(this.ctx.font.match(/\d+/), 10); | |
const textWidth = this.ctx.measureText(subnetAddress).width; | |
const textX = 10; //((this.CONFIG.borderLeft/2) - (textWidth/2)); | |
const textY = lineY + ((this.CONFIG.cellHeight/2) + (textHeight/2)); | |
this.ctx.fillText(subnetAddress,textX,textY); | |
} | |
this.ctx.stroke(); | |
} | |
drawPrefixesRect() { | |
this.prefixes.sort((a, b) => a.network.split('/')[1] - b.network.split('/')[1]); | |
this.prefixes.forEach((prefix) => { | |
var [prefixBase, prefixLength] = prefix.network.split("/"); | |
var color = this.CONFIG.colors.cidr.find(c => c.cidr == prefixLength).color; | |
var [baseA, baseB, baseC, baseD] = prefixBase.split("."); | |
var startRow = Math.floor((parseInt(baseC) * 256 + parseInt(baseD)) / 256); | |
var numRows = Math.pow(2, (24 - parseInt(prefixLength))); | |
var numCols = Math.min(Math.pow(2, (32 - parseInt(prefixLength))), 256); | |
var rectX = this.CONFIG.borderLeft + (parseInt(baseD) * this.CONFIG.cellWidth); | |
var rectY = startRow * this.CONFIG.cellHeight + this.CONFIG.borderTop; | |
var rectWidth = numCols * this.CONFIG.cellWidth; | |
var rectHeight = this.CONFIG.cellHeight * numRows; | |
// Correcting the height for this.prefixes smaller than /24 | |
if (parseInt(prefixLength) > 24) { | |
rectHeight = this.CONFIG.cellHeight; | |
} | |
// Draw the rectangle | |
this.ctx.beginPath(); | |
this.ctx.fillStyle = color; | |
this.ctx.strokeStyle = 'black'; | |
this.ctx.rect(rectX, rectY, rectWidth, rectHeight); | |
this.ctx.fill(); | |
this.ctx.stroke(); | |
this.ctx.closePath(); | |
// Store drawn rectangle | |
this.prefixRects.push({ | |
x: rectX, | |
y: rectY, | |
width: rectWidth, | |
height: rectHeight, | |
prefix: prefix | |
}); | |
// Drawing the text on the rectangle if possible. | |
this.ctx.font = this.CONFIG.fonts.desc; | |
var textHeight = parseInt(this.ctx.font.match(/\d+/), 10); | |
var textWidth = this.ctx.measureText(prefix.description).width; | |
if (textHeight <= rectHeight && textWidth < rectWidth) { | |
var textX = rectX + (rectWidth - textWidth) / 2; | |
var textY = rectY + (rectHeight + textHeight) / 2 - 4; | |
this.ctx.fillStyle = this.CONFIG.colors.desc; | |
this.ctx.fillText(prefix.description, textX, textY); | |
} | |
}); | |
} | |
getPrefix(x, y) { | |
for(let i = this.prefixRects.length - 1; i >= 0; i--) { | |
let rect = this.prefixRects[i]; | |
if(y > rect.y && y < rect.y + rect.height && x > rect.x && x < rect.x + rect.width) { | |
return rect.prefix; | |
} | |
} | |
return null; | |
} | |
} | |
let prefixes = [ | |
{ network: "192.168.0.128/25", description: "foo1" }, | |
{ network: "192.168.0.0/24", description: "foo2" }, | |
{ network: "192.168.4.0/23", description: "foo3"} | |
]; | |
let prefixes2 = [ | |
{ network: "192.168.0.64/26", description: "foo4" }, | |
{ network: "192.168.3.0/24", description: "foo5" }, | |
{ network: "192.168.10.0/23", description: "foo3"} | |
]; | |
let canvas = document.getElementById('ipam'); | |
let ipam = new DrawIPAM("192.168.0.0/20", canvas); | |
ipam.addPrefixes(prefixes); | |
ipam.addPrefixes(prefixes2); | |
ipam.removePrefix("192.168.0.128/25"); | |
ipam.removePrefixes([{ network: "192.168.3.0/24", description: "foo5" }]); | |
ipam.addPrefix({ network: "192.168.14.64/26", description: "foo6" }); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment