2024-04-07 05:03:13 -04:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > IP Map< / title >
2024-04-16 04:21:25 -04:00
< script src = "https://cdn.7cs.to/files/js/maplibre-gl.js" > < / script >
< link rel = "stylesheet" href = "https://cdn.7cs.to/files/css/maplibre-gl.css" / >
< link rel = "shortcut icon" href = "assets/7circles-pastel-logo.svg" / >
2024-04-07 05:03:13 -04:00
< style >
body {
margin: 0;
padding: 0;
background-color: #111;
}
html, body {
width: 100%;
height: 100%;
}
main {
height: 100%;
2024-04-11 01:32:42 -04:00
width: 100%;
2024-04-07 05:03:13 -04:00
display: flex;
2024-04-11 01:32:42 -04:00
flex-direction: column;
2024-04-07 05:03:13 -04:00
align-items: center;
font-family: sans-serif;
}
2024-04-11 01:32:42 -04:00
#top-inputs {
width: 20rem;
max-width: calc(100% - 2rem);
margin-top: 1rem;
display: flex;
align-items: center;
justify-content: center;
}
#range-input {
flex-grow: 1;
min-width: 0;
}
#range-input, #range-button {
box-sizing: border-box;
font-size: 1rem;
padding: 0.5rem 0.7rem;
border: 0;
border-radius: 0.25rem;
}
#range-button {
margin-left: 0.5rem;
cursor: pointer;
2024-04-12 09:06:30 -04:00
color: black;
2024-04-11 01:32:42 -04:00
background-color: rgb(255, 176, 176);
}
#map-container {
flex-grow: 1;
aspect-ratio: 1 / 1;
max-height: 100vw;
margin: auto 0;
}
2024-04-07 05:03:13 -04:00
#map {
2024-04-11 01:32:42 -04:00
width: calc(100% - 2rem);
height: calc(100% - 2rem);
margin: 1rem;
2024-04-07 05:03:13 -04:00
}
.maplibregl-canvas {
cursor: pointer;
}
.maplibregl-popup {
max-width: unset !important;
}
.maplibregl-popup-content {
2024-04-07 08:00:56 -04:00
background-color: white;
2024-04-07 05:03:13 -04:00
color: black;
2024-04-07 08:00:56 -04:00
font-size: 0.9rem;
padding: 0.3rem 2.2rem 0.3rem 0.6rem;
border-radius: 4px;
2024-04-07 05:03:13 -04:00
}
.maplibregl-popup-close-button {
height: 100%;
2024-04-07 08:00:56 -04:00
width: 2rem;
2024-04-07 05:03:13 -04:00
margin: 0;
padding: 0;
2024-04-07 08:00:56 -04:00
padding-bottom: 3px;
font-size: 1.3rem;
color: black;
2024-04-07 05:03:13 -04:00
}
2024-04-09 04:05:36 -04:00
.maplibregl-ctrl-top-right {
display: flex;
}
.maplibregl-ctrl-top-right .custom-control {
align-self: flex-start;
}
.maplibregl-ctrl button.maplibregl-ctrl-subnets .maplibregl-ctrl-icon {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' stroke='%23333' stroke-width='1.5' fill='none' viewBox='0 0 29 29'%3E%3Cpath d='m 6 6 v 17 h 17 v -17 h -17 z m 0 8.5 h 17 m -8.5 -8.5 v 17'/%3E%3C/svg%3E");
}
2024-04-07 05:03:13 -04:00
.custom-control {
user-select: none;
}
2024-04-09 04:05:36 -04:00
details.custom-control > summary {
2024-04-07 05:03:13 -04:00
margin: 0;
2024-04-09 04:05:36 -04:00
padding: 0 0.5rem;
display: flex;
align-items: center;
height: 29px;
2024-04-07 05:03:13 -04:00
cursor: pointer;
}
2024-04-09 04:05:36 -04:00
details.custom-control[open] > summary {
2024-04-07 05:03:13 -04:00
border-bottom: 2px solid black;
}
2024-04-09 04:05:36 -04:00
details.custom-control > summary::marker {
content: "";
}
details.custom-control > summary {
list-style: none;
}
details.custom-control > summary::-webkit-details-marker {
display:none;
}
details.custom-control > summary h3 {
2024-04-07 05:03:13 -04:00
margin: 0;
padding: 0;
display: inline;
2024-04-09 04:05:36 -04:00
font-size: 0.9rem;
}
details.custom-control > .button-container {
max-height: 12rem;
2024-04-20 03:39:56 -04:00
overflow-y: auto;
2024-04-07 05:03:13 -04:00
}
2024-04-09 04:05:36 -04:00
details.custom-control > .button-container button {
2024-04-07 05:03:13 -04:00
width: 100%;
padding: 0 0.5rem;
2024-04-09 04:05:36 -04:00
}
2024-04-09 06:50:05 -04:00
details.custom-control > .button-container button.active {
pointer-events: none;
}
2024-04-09 04:05:36 -04:00
.maplibregl-ctrl button {
2024-04-07 05:03:13 -04:00
color: black;
2024-04-09 04:05:36 -04:00
font-size: 0.8rem;
2024-04-07 05:03:13 -04:00
}
2024-04-09 04:05:36 -04:00
.maplibregl-ctrl button.active {
2024-04-07 05:03:13 -04:00
background-color: rgb(0 0 0/15%);
}
.maplibregl-ctrl button:not(:disabled):hover {
background-color: rgb(0 0 0/10%);
}
.maplibregl-ctrl button:not(:disabled):active {
background-color: rgb(0 0 0/15%);
}
2024-04-09 04:05:36 -04:00
.maplibregl-ctrl p {
margin: 0.25rem 0.5rem;
font-size: 0.8rem;
}
.maplibregl-ctrl p:empty {
display: none;
}
2024-04-07 05:03:13 -04:00
< / style >
< / head >
< body >
< main >
2024-04-11 01:32:42 -04:00
< div id = "top-inputs" > < input type = "text" id = "range-input" name = "range" minlength = "7" maxlength = "18" placeholder = "1.1.1.1" / > < button id = "range-button" > Jump< / button > < / div >
< div id = "map-container" > < div id = "map" > < / div > < / div >
2024-04-07 05:03:13 -04:00
< / main >
< script >
const coordsToHilbert = ({ x, y }) => {
let rotation = 0
let reflection = 0
let index = 0
for (let b = 15; b >= 0; b--) {
let bits = reflection
reflection = (y >>> b) & 1
reflection |= ((x >>> b) & 1) < < 1
bits = bits ^ reflection
bits = ((bits >>> rotation) | (bits < < (2 - rotation))) & 3
index |= bits < < (b < < 1 )
reflection ^= (1 < < rotation )
bits = bits & (-bits) & 1
while (bits) {
++rotation
bits >>>= 1
}
if (++rotation >= 2)
rotation -= 2
}
index ^= 0x2aaaaaaa
for (let d = 1; d < 32 ; d * = 2 ) {
let t = index >>> d
if (!t) break
index ^= t
}
return index
}
const hilbertToCoords = index => {
let rotation = 0
let reflection = 0
let coord = { x: 0, y: 0 }
index ^= (index >>> 1) ^ 0x2aaaaaaa
for (let b = 15; b >= 0; b--) {
var bits = index >>> (2 * b) & 3
reflection ^= ((bits >>> (2 - rotation)) | (bits < < rotation ) ) & 3
coord.y |= (reflection & 1) < < b
coord.x |= ((reflection >>> 1) & 1) < < b
reflection ^= (1 < < rotation )
bits & = (-bits) & 1
while (bits) {
bits >>>= 1
++rotation
}
if (++rotation >= 2)
rotation -= 2
}
return coord
}
2024-04-11 01:32:42 -04:00
const normalizeIp = (ip, subnet) => (ip >> (32 - subnet)) < < (32 - subnet)
2024-04-09 04:05:36 -04:00
const ipToString = (ip, subnet = 32) => {
2024-04-11 01:32:42 -04:00
const x = normalizeIp(ip, subnet)
2024-04-09 04:05:36 -04:00
return `${x >> 24 & 0xFF}.${x >> 16 & 0xFF}.${x >> 8 & 0xFF}.${x >> 0 & 0xFF}`
}
2024-04-07 05:03:13 -04:00
2024-04-11 01:32:42 -04:00
const ipFromString = ipStr => {
const m = ipStr.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,2}))?$/)
if (!m) throw new Error("Invalid IP or Range format")
const [a, b, c, d] = m.slice(1, 5).map(Number)
const subnet = m[5] ? Number(m[5]) : 32
if (a > 255 || b > 255 || c > 255 || d > 255 || subnet > 32) throw new Error("Invalid IP or Range format")
const ip = normalizeIp(a < < 24 | b < < 16 | c < < 8 | d , subnet )
return { ip, subnet }
2024-04-07 05:03:13 -04:00
}
2024-04-11 01:51:09 -04:00
const apiUrl = "https://ipapi.7circles.moe"
2024-04-20 03:39:56 -04:00
const defaultVariant = "rtt"
2024-04-15 09:16:04 -04:00
const defaultColormap = "viridis"
2024-04-08 08:59:54 -04:00
const tileSize = 256
2024-04-07 05:03:13 -04:00
const tilesDir = "assets/tiles"
const tilesId = "ipmap-tiles"
const privateRangesId = "private-ranges"
2024-04-08 08:59:54 -04:00
const subnetsId = "subnets"
2024-04-07 05:03:13 -04:00
2024-04-11 01:32:42 -04:00
const privateRanges = [
{ range: "0.0.0.0/8", description: "Current Network Definition" },
{ range: "10.0.0.0/8", description: "Private Networks" },
{ range: "100.64.0.0/10", description: "CGNAT Transition" },
{ range: "127.0.0.0/8", description: "Loopback" },
{ range: "169.254.0.0/16", description: "Link-Local" },
{ range: "172.16.0.0/12", description: "Private Networks" },
{ range: "192.0.0.0/24", description: "DS-Lite Transition" },
{ range: "192.0.2.0/24", description: "Documentation" },
{ range: "192.88.99.0/24", description: "IPv4 to IPv6 Transition" },
{ range: "192.168.0.0/16", description: "Private Networks" },
{ range: "198.18.0.0/15", description: "Testing" },
{ range: "198.51.100.0/24", description: "Documentation" },
{ range: "203.0.113.0/24", description: "Documentation" },
{ range: "224.0.0.0/4", description: "Multicast (Class D)" },
{ range: "240.0.0.0/4", description: "Class E" },
].map(r => ({ ...r, ...ipFromString(r.range) }))
const getPrivateRange = searchIp =>
privateRanges.find(({ ip, subnet }) => normalizeIp(searchIp, subnet) == ip)
2024-04-08 08:59:54 -04:00
const canvas = document.createElement("canvas")
2024-04-11 01:32:42 -04:00
canvas.width = tileSize * devicePixelRatio
canvas.height = tileSize * devicePixelRatio
2024-04-08 08:59:54 -04:00
const ctx = canvas.getContext("2d")
2024-04-11 01:32:42 -04:00
ctx.scale(devicePixelRatio, devicePixelRatio)
2024-04-08 08:59:54 -04:00
ctx.fillStyle = "white"
ctx.lineWidth = 3
ctx.font = "bold 20px sans-serif"
maplibregl.addProtocol(subnetsId, async (params, abortController) => {
const [z, y, x] = params.url.split("://")[1].split("/")
const width = 2 ** z
const subnet = 2 * z
const hy = Math.floor(0x10000 * (y / width))
const hx = Math.floor(0x10000 * (x / width))
2024-04-09 04:05:36 -04:00
const text = `${ipToString(coordsToHilbert({ x: hx, y: hy }), subnet)}/${subnet}`
2024-04-08 08:59:54 -04:00
const metrics = ctx.measureText(text)
const cy = tileSize / 2 + metrics.actualBoundingBoxAscent / 2
const cx = tileSize / 2 - metrics.actualBoundingBoxRight / 2
ctx.clearRect(0, 0, tileSize, tileSize)
ctx.moveTo(0, 0)
ctx.lineTo(tileSize, 0)
ctx.lineTo(tileSize, tileSize)
ctx.lineTo(0, tileSize)
ctx.lineTo(0, 0)
ctx.strokeStyle = "white"
ctx.stroke()
ctx.strokeStyle = "black"
ctx.strokeText(text, cx, cy)
ctx.fillText(text, cx, cy)
return { data: await new Promise((res, rej) => canvas.toBlob(b => b.arrayBuffer().then(res))) }
})
2024-04-09 04:05:36 -04:00
const map = new maplibregl.Map({
container: "map",
attributionControl: false,
renderWorldCopies: false,
doubleClickZoom: false,
dragRotate: false,
pitchWithRotate: false,
touchPitch: false,
style: {
version: 8,
sources: {},
layers: []
},
center: [0, 0],
minZoom: -2,
maxZoom: 11.499,
zoom: -2
})
map.painter.context.extTextureFilterAnisotropic = undefined
map.touchZoomRotate.disableRotation()
const dataP = fetch(`${tilesDir}/tiles.json`, { cache: "no-store" }).then(res => res.json())
const privateRangePatternP = map.loadImage("assets/private-range-pattern.png")
2024-04-07 05:03:13 -04:00
map.once("style.load", async () => {
const data = await dataP
2024-04-09 06:50:05 -04:00
// remove empty keys and sort colormaps alphabetically
for (const date in data) {
for (const variant in data[date]) {
if (data[date][variant].length === 0)
delete data[date][variant]
else
data[date][variant] = data[date][variant].sort()
}
if (Object.keys(data[date]).length === 0)
delete data[date]
}
2024-04-09 04:05:36 -04:00
const dates = Object.keys(data).sort()
2024-04-09 06:50:05 -04:00
if (dates.length === 0) {
2024-04-09 04:05:36 -04:00
console.error("no data found")
2024-04-07 05:03:13 -04:00
return
}
2024-04-09 06:50:05 -04:00
let curDate, curVariant, curColormap
const resetDate = () => curDate = dates[dates.length - 1]
const resetVariant = () => curVariant = defaultVariant in data[curDate] ? defaultVariant : Object.keys(data[curDate]).sort()[0]
const resetColormap = () => curColormap = data[curDate][curVariant].includes(defaultColormap) ? defaultColormap : data[curDate][curVariant][0]
resetDate()
resetVariant()
resetColormap()
const getTileUrl = () => `${tilesDir}/${curDate}/${curVariant}/${curColormap}/{z}/{y}/{x}.png`
2024-04-07 05:03:13 -04:00
map.addSource(tilesId, {
type: "raster",
2024-04-09 06:50:05 -04:00
tiles: [getTileUrl()],
2024-04-08 08:59:54 -04:00
tileSize,
2024-04-07 05:03:13 -04:00
minzoom: 0,
maxzoom: 8,
})
map.addLayer({
id: tilesId,
type: "raster",
source: tilesId,
paint: {
"raster-resampling": "nearest"
}
})
map.addSource(privateRangesId, {
type: "geojson",
data: {
type: "FeatureCollection",
2024-04-11 01:32:42 -04:00
features: privateRanges.map(({ range, description, ip, subnet }) => {
const coordinates = [(() => {
if (subnet % 2 === 0) {
const subnetSize = 2 ** (32 - subnet)
const subnetSideSize = Math.sqrt(subnetSize)
const subnetsPerSide = 0x10000 / subnetSideSize
const { x: hx, y: hy } = hilbertToCoords(ip + Math.floor(subnetSize / 2))
const tx = Math.floor(hx / subnetSideSize)
const ty = Math.floor(hy / subnetSideSize)
const mxA = tx / subnetsPerSide
const mxB = (tx + 1) / subnetsPerSide
const myA = ty / subnetsPerSide
const myB = (ty + 1) / subnetsPerSide
return [
new maplibregl.MercatorCoordinate(mxA, myA, 0).toLngLat().toArray(),
new maplibregl.MercatorCoordinate(mxB, myA, 0).toLngLat().toArray(),
new maplibregl.MercatorCoordinate(mxB, myB, 0).toLngLat().toArray(),
new maplibregl.MercatorCoordinate(mxA, myB, 0).toLngLat().toArray(),
new maplibregl.MercatorCoordinate(mxA, myA, 0).toLngLat().toArray(),
]
} else {
const nearestSubnet = subnet + 1
const subnetSize = 2 ** (32 - nearestSubnet)
const subnetSideSize = Math.sqrt(subnetSize)
const subnetsPerSide = 0x10000 / subnetSideSize
const { x: hxA, y: hyA } = hilbertToCoords(ip + subnetSize / 2)
const { x: hxB, y: hyB } = hilbertToCoords(ip + subnetSize + subnetSize / 2)
const txA = Math.floor(hxA / subnetSideSize)
const tyA = Math.floor(hyA / subnetSideSize)
const txB = Math.floor(hxB / subnetSideSize)
const tyB = Math.floor(hyB / subnetSideSize)
const mxA = Math.min(txA, txB) / subnetsPerSide
const mxB = (Math.max(txA, txB) + 1) / subnetsPerSide
const myA = Math.min(tyA, tyB) / subnetsPerSide
const myB = (Math.max(tyA, tyB) + 1) / subnetsPerSide
return [
new maplibregl.MercatorCoordinate(mxA, myA, 0).toLngLat().toArray(),
new maplibregl.MercatorCoordinate(mxB, myA, 0).toLngLat().toArray(),
new maplibregl.MercatorCoordinate(mxB, myB, 0).toLngLat().toArray(),
new maplibregl.MercatorCoordinate(mxA, myB, 0).toLngLat().toArray(),
new maplibregl.MercatorCoordinate(mxA, myA, 0).toLngLat().toArray(),
]
}
})()]
return {
type: "Feature",
geometry: {
type: "Polygon",
coordinates
},
properties: {
range,
description
}
2024-04-07 05:03:13 -04:00
}
2024-04-11 01:32:42 -04:00
})
2024-04-07 05:03:13 -04:00
}
})
const privateRangePatternId = "private-range-pattern"
map.addImage(privateRangePatternId, (await privateRangePatternP).data)
map.addLayer({
id: privateRangesId,
type: "fill",
source: privateRangesId,
paint: {
"fill-pattern": privateRangePatternId
}
})
2024-04-09 04:05:36 -04:00
let subnetsVisible = true
2024-04-08 08:59:54 -04:00
map.addSource(subnetsId, {
type: "raster",
tiles: [`${subnetsId}://{z}/{y}/{x}`],
tileSize,
2024-04-09 04:05:36 -04:00
minzoom: 0,
maxzoom: 12,
2024-04-08 08:59:54 -04:00
})
map.addLayer({
id: subnetsId,
type: "raster",
source: subnetsId,
2024-04-07 05:03:13 -04:00
})
2024-04-09 06:50:05 -04:00
const updateTileUrl = () => map.getSource(tilesId).setTiles([getTileUrl()])
2024-04-07 05:03:13 -04:00
2024-04-09 04:05:36 -04:00
const detailsControl = (name) => {
2024-04-07 05:03:13 -04:00
const container = document.createElement("details")
container.className = "maplibregl-ctrl maplibregl-ctrl-group custom-control"
const header = document.createElement("h3")
header.textContent = name
const summary = document.createElement("summary")
summary.replaceChildren(header)
2024-04-09 04:05:36 -04:00
const buttonContainer = document.createElement("div")
buttonContainer.className = "button-container"
container.replaceChildren(summary, buttonContainer)
return {
container,
setButtons: buttons => buttonContainer.replaceChildren(...buttons.map(({ button }) => button)),
2024-04-11 01:32:42 -04:00
addControl: () => map.addControl({
onAdd: () => container,
onRemove: () => container.parentNode.removeChild(container)
}, "top-right")
2024-04-09 04:05:36 -04:00
}
2024-04-07 05:03:13 -04:00
}
2024-04-09 04:05:36 -04:00
const dateControl = detailsControl("Date")
const variantControl = detailsControl("Variant")
const colormapControl = detailsControl("Colormap")
2024-04-07 05:03:13 -04:00
const dateButtons = dates.map(date => {
const button = document.createElement("button")
2024-04-09 07:58:17 -04:00
button.textContent = `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`
2024-04-07 05:03:13 -04:00
if (curDate === date) button.className = "active"
return { date, button }
})
dateButtons.forEach(({ date, button }) => button.addEventListener("click", () => {
2024-04-09 06:50:05 -04:00
curDate = date
if (!(curVariant in data[curDate])) resetVariant()
if (!data[curDate][curVariant].includes(curColormap)) resetColormap()
updateTileUrl()
dateButtons.forEach(({ button }) => button.className = "")
button.className = "active"
renderControls()
2024-04-07 05:03:13 -04:00
}))
dateControl.setButtons(dateButtons)
const renderControls = () => {
const variants = Object.keys(data[curDate]).sort()
const variantButtons = variants.map(variant => {
const button = document.createElement("button")
button.textContent = variant
if (curVariant === variant) button.className = "active"
return { variant, button }
})
variantButtons.forEach(({ variant, button }) => button.addEventListener("click", () => {
2024-04-09 06:50:05 -04:00
curVariant = variant
if (!data[curDate][curVariant].includes(curColormap)) resetColormap()
updateTileUrl()
renderControls()
2024-04-07 05:03:13 -04:00
}))
variantControl.setButtons(variantButtons)
2024-04-09 06:50:05 -04:00
const colormaps = data[curDate][curVariant]
2024-04-07 05:03:13 -04:00
const colormapButtons = colormaps.map(colormap => {
const button = document.createElement("button")
button.textContent = colormap
if (curColormap === colormap) button.className = "active"
return { colormap, button }
})
colormapButtons.forEach(({ colormap, button }) => button.addEventListener("click", () => {
2024-04-09 06:50:05 -04:00
curColormap = colormap
updateTileUrl()
colormapButtons.forEach(({ button }) => button.className = "")
button.className = "active"
2024-04-07 05:03:13 -04:00
}))
colormapControl.setButtons(colormapButtons)
}
renderControls()
2024-04-09 04:05:36 -04:00
const subnetsControl = document.createElement("div")
subnetsControl.className = "maplibregl-ctrl maplibregl-ctrl-group custom-control"
const subnetsButton = document.createElement("button")
subnetsButton.className = "maplibregl-ctrl-subnets active"
const subnetsIcon = document.createElement("span")
subnetsIcon.className = "maplibregl-ctrl-icon"
subnetsButton.replaceChildren(subnetsIcon)
subnetsButton.addEventListener("click", () => {
subnetsVisible = !subnetsVisible
subnetsButton.classList.toggle("active")
map.setLayoutProperty(subnetsId, "visibility", subnetsVisible ? "visible" : "none")
})
subnetsControl.replaceChildren(subnetsButton)
2024-04-11 01:32:42 -04:00
map.addControl({
onAdd: () => subnetsControl,
onRemove: () => subnetsControl.parentNode.removeChild(subnetsControl)
}, "top-right")
2024-04-07 05:03:13 -04:00
dateControl.addControl()
variantControl.addControl()
colormapControl.addControl()
2024-04-11 01:32:42 -04:00
})
2024-04-07 05:03:13 -04:00
2024-04-11 01:32:42 -04:00
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), "top-left")
const hoverTextControl = document.createElement("div")
hoverTextControl.className = "maplibregl-ctrl maplibregl-ctrl-group"
const hoverTextP = document.createElement("p")
hoverTextControl.replaceChildren(hoverTextP)
const ipFromMouseEvent = e => {
const { x, y } = maplibregl.MercatorCoordinate.fromLngLat(e.lngLat, 0)
const ip = coordsToHilbert({ x: Math.floor(0x10000 * x), y: Math.floor(0x10000 * y) })
const subnet = Math.min(32, Math.round(map.getZoom()) * 2 + 18)
return { ip, subnet }
}
map.on("mousemove", e => {
const { ip, subnet } = ipFromMouseEvent(e)
const ipStr = ipToString(ip, subnet)
hoverTextP.textContent = subnet < 32 ? ` Range: $ { ipStr } / $ { subnet } ` : ` IP: $ { ipStr } `
})
map.addControl({
onAdd: () => hoverTextControl,
onRemove: () => hoverTextControl.parentNode.removeChild(hoverTextControl)
}, "bottom-left")
let curPopup
const setPopup = (pos, ip, subnet = 32) => {
const isRange = subnet < 32
const name = isRange ? "Range" : "IP"
const ipStr = ipToString(ip, subnet)
const ipText = isRange ? `${ipStr}/${subnet}` : ipStr
const ipLink = `< a href = "https://bgp.tools/prefix/${ipStr}" target = "_blank" > ${ipText}< / a > `
const htmlBase = `${name}: ${ipLink}`
const privateRange = getPrivateRange(ip)
const html = privateRange ? `${htmlBase}< br > Part of private range ${privateRange.range}< br > Used for ${privateRange.description}` : htmlBase
curPopup?.remove()
const popup = new maplibregl.Popup({ focusAfterOpen: false }).setHTML(html).setLngLat(pos).addTo(map)
curPopup = popup
if (!isRange & & !privateRange) {
fetch(`${apiUrl}/api/rdns/${ipStr}`).then(res => {
if (!res.ok)
throw new Error(`Error fetching rdns for ip ${ipStr}`)
return res.json()
}).then(data => {
const rdns = data?.rdns
if (rdns) popup.setHTML(`${html}< br > rDNS: ${rdns}`)
}).catch(_ => {})
}
}
map.on("click", e => {
const { ip, subnet } = ipFromMouseEvent(e)
setPopup(e.lngLat, ip, subnet)
})
const main = document.getElementsByTagName("main")[0]
main.addEventListener("click", e => {
if (e.target === main & & curPopup) curPopup.remove()
2024-04-07 08:00:56 -04:00
})
2024-04-11 01:32:42 -04:00
const rangeInput = document.getElementById("range-input")
const jumpParam = new URLSearchParams(location.search).get("jump")
const jump = range => {
const { ip, subnet } = ipFromString(range)
const { x, y } = hilbertToCoords(ip + Math.floor((2 ** (32 - subnet)) / 2))
const center = new maplibregl.MercatorCoordinate((x + 0.5) / 0x10000, (y + 0.5) / 0x10000, 0).toLngLat()
const zoom = subnet / 2 - 0.501
map.jumpTo({ center, zoom })
if (subnet > 24) setPopup(center, ip, subnet)
}
const setJumpParam = jump => {
const searchParams = new URLSearchParams(location.search)
if (jump) searchParams.set("jump", jump)
else searchParams.delete("jump")
history.pushState("", "", searchParams.size === 0 ? location.pathname : `?${searchParams}`)
}
try {
jump(jumpParam)
rangeInput.value = jumpParam
} catch(e) {
setJumpParam(undefined)
}
2024-04-11 01:49:41 -04:00
const jumpToInput = () => {
try {
2024-04-11 01:53:40 -04:00
if (rangeInput.value) jump(rangeInput.value)
2024-04-11 01:49:41 -04:00
setJumpParam(rangeInput.value)
rangeInput.setCustomValidity("")
} catch (e) {
rangeInput.setCustomValidity(e.message)
}
rangeInput.reportValidity()
}
rangeInput.addEventListener("change", jumpToInput)
document.getElementById("range-button").addEventListener("click", jumpToInput)
2024-04-07 05:03:13 -04:00
< / script >
< / body >
< / html >