Improve webpage
This commit is contained in:
parent
f939fbe2f7
commit
3d3a437ed2
|
@ -19,14 +19,46 @@
|
||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
#top-inputs {
|
||||||
|
width: 20rem;
|
||||||
|
max-width: calc(100% - 2rem);
|
||||||
|
margin-top: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-family: sans-serif;
|
}
|
||||||
|
#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;
|
||||||
|
background-color: rgb(255, 176, 176);
|
||||||
|
}
|
||||||
|
#map-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
max-height: 100vw;
|
||||||
|
margin: auto 0;
|
||||||
}
|
}
|
||||||
#map {
|
#map {
|
||||||
width: min(90vw, 90vh);
|
width: calc(100% - 2rem);
|
||||||
height: min(90vw, 90vh);
|
height: calc(100% - 2rem);
|
||||||
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
.maplibregl-canvas {
|
.maplibregl-canvas {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -123,7 +155,8 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<div id="map"></div>
|
<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>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
const coordsToHilbert = ({ x, y }) => {
|
const coordsToHilbert = ({ x, y }) => {
|
||||||
|
@ -177,57 +210,25 @@
|
||||||
return coord
|
return coord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeIp = (ip, subnet) => (ip >> (32 - subnet)) << (32 - subnet)
|
||||||
|
|
||||||
const ipToString = (ip, subnet = 32) => {
|
const ipToString = (ip, subnet = 32) => {
|
||||||
const x = (ip >> (32 - subnet)) << (32 - subnet)
|
const x = normalizeIp(ip, subnet)
|
||||||
return `${x >> 24 & 0xFF}.${x >> 16 & 0xFF}.${x >> 8 & 0xFF}.${x >> 0 & 0xFF}`
|
return `${x >> 24 & 0xFF}.${x >> 16 & 0xFF}.${x >> 8 & 0xFF}.${x >> 0 & 0xFF}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeToCoords = range => {
|
const ipFromString = ipStr => {
|
||||||
const [a, b, c, d, subnet] = range.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/).slice(1).map(Number)
|
const m = ipStr.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,2}))?$/)
|
||||||
const ip = a << 24 | b << 16 | c << 8 | d
|
if (!m) throw new Error("Invalid IP or Range format")
|
||||||
if (subnet % 2 === 0) {
|
const [a, b, c, d] = m.slice(1, 5).map(Number)
|
||||||
const subnetSize = 2 ** (32 - subnet)
|
const subnet = m[5] ? Number(m[5]) : 32
|
||||||
const subnetSideSize = Math.sqrt(subnetSize)
|
if (a > 255 || b > 255 || c > 255 || d > 255 || subnet > 32) throw new Error("Invalid IP or Range format")
|
||||||
const subnetsPerSide = 0x10000 / subnetSideSize
|
const ip = normalizeIp(a << 24 | b << 16 | c << 8 | d, subnet)
|
||||||
const { x: hx, y: hy } = hilbertToCoords(ip + subnetSize / 2)
|
return { ip, subnet }
|
||||||
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(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const apiUrl = "https://ipapi.7circles.moe"
|
||||||
|
const apiUrl = "http://code.chromatic.garden:8000"
|
||||||
const defaultVariant = "density"
|
const defaultVariant = "density"
|
||||||
const defaultColormap = "jet"
|
const defaultColormap = "jet"
|
||||||
const tileSize = 256
|
const tileSize = 256
|
||||||
|
@ -236,12 +237,32 @@
|
||||||
const privateRangesId = "private-ranges"
|
const privateRangesId = "private-ranges"
|
||||||
const subnetsId = "subnets"
|
const subnetsId = "subnets"
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
const canvas = document.createElement("canvas")
|
const canvas = document.createElement("canvas")
|
||||||
const canvasScale = window.devicePixelRatio
|
canvas.width = tileSize * devicePixelRatio
|
||||||
canvas.width = tileSize * canvasScale
|
canvas.height = tileSize * devicePixelRatio
|
||||||
canvas.height = tileSize * canvasScale
|
|
||||||
const ctx = canvas.getContext("2d")
|
const ctx = canvas.getContext("2d")
|
||||||
ctx.scale(canvasScale, canvasScale)
|
ctx.scale(devicePixelRatio, devicePixelRatio)
|
||||||
ctx.fillStyle = "white"
|
ctx.fillStyle = "white"
|
||||||
ctx.lineWidth = 3
|
ctx.lineWidth = 3
|
||||||
ctx.font = "bold 20px sans-serif"
|
ctx.font = "bold 20px sans-serif"
|
||||||
|
@ -338,39 +359,67 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
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.addSource(privateRangesId, {
|
map.addSource(privateRangesId, {
|
||||||
type: "geojson",
|
type: "geojson",
|
||||||
data: {
|
data: {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: privateRanges.map(({ range, description }) => ({
|
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",
|
type: "Feature",
|
||||||
geometry: {
|
geometry: {
|
||||||
type: "Polygon",
|
type: "Polygon",
|
||||||
coordinates: [rangeToCoords(range)]
|
coordinates
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
range,
|
range,
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -401,11 +450,6 @@
|
||||||
|
|
||||||
const updateTileUrl = () => map.getSource(tilesId).setTiles([getTileUrl()])
|
const updateTileUrl = () => map.getSource(tilesId).setTiles([getTileUrl()])
|
||||||
|
|
||||||
const addControl = (elem, side) => map.addControl({
|
|
||||||
onAdd: () => elem,
|
|
||||||
onRemove: () => elem.parentNode.removeChild(elem)
|
|
||||||
}, side)
|
|
||||||
|
|
||||||
const detailsControl = (name) => {
|
const detailsControl = (name) => {
|
||||||
const container = document.createElement("details")
|
const container = document.createElement("details")
|
||||||
container.className = "maplibregl-ctrl maplibregl-ctrl-group custom-control"
|
container.className = "maplibregl-ctrl maplibregl-ctrl-group custom-control"
|
||||||
|
@ -419,7 +463,10 @@
|
||||||
return {
|
return {
|
||||||
container,
|
container,
|
||||||
setButtons: buttons => buttonContainer.replaceChildren(...buttons.map(({ button }) => button)),
|
setButtons: buttons => buttonContainer.replaceChildren(...buttons.map(({ button }) => button)),
|
||||||
addControl: (side = "top-right") => addControl(container, side)
|
addControl: () => map.addControl({
|
||||||
|
onAdd: () => container,
|
||||||
|
onRemove: () => container.parentNode.removeChild(container)
|
||||||
|
}, "top-right")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,42 +538,106 @@
|
||||||
map.setLayoutProperty(subnetsId, "visibility", subnetsVisible ? "visible" : "none")
|
map.setLayoutProperty(subnetsId, "visibility", subnetsVisible ? "visible" : "none")
|
||||||
})
|
})
|
||||||
subnetsControl.replaceChildren(subnetsButton)
|
subnetsControl.replaceChildren(subnetsButton)
|
||||||
addControl(subnetsControl, "top-right")
|
map.addControl({
|
||||||
|
onAdd: () => subnetsControl,
|
||||||
|
onRemove: () => subnetsControl.parentNode.removeChild(subnetsControl)
|
||||||
|
}, "top-right")
|
||||||
|
|
||||||
dateControl.addControl()
|
dateControl.addControl()
|
||||||
variantControl.addControl()
|
variantControl.addControl()
|
||||||
colormapControl.addControl()
|
colormapControl.addControl()
|
||||||
|
})
|
||||||
|
|
||||||
const getIPAtMouse = (e, features, link) => {
|
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), "top-left")
|
||||||
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)
|
|
||||||
const ipStr = ipToString(ip, subnet)
|
|
||||||
const props = features && map.queryRenderedFeatures(e.point).find(f => f.layer.id === privateRangesId)?.properties
|
|
||||||
const propsText = props ? `<br>Part of private range ${props.range}<br>Used for ${props.description}` : ""
|
|
||||||
const name = subnet < 32 ? "Range" : "IP"
|
|
||||||
const ipText = subnet < 32 ? `${ipStr}/${subnet}` : ipStr
|
|
||||||
const ipLink = link ? `<a href="https://bgp.tools/prefix/${ipStr}" target="_blank">${ipText}</a>` : ipText
|
|
||||||
return `${name}: ${ipLink}${propsText}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const hoverTextControl = document.createElement("div")
|
const hoverTextControl = document.createElement("div")
|
||||||
hoverTextControl.className = "maplibregl-ctrl maplibregl-ctrl-group"
|
hoverTextControl.className = "maplibregl-ctrl maplibregl-ctrl-group"
|
||||||
const hoverTextP = document.createElement("p")
|
const hoverTextP = document.createElement("p")
|
||||||
hoverTextControl.replaceChildren(hoverTextP)
|
hoverTextControl.replaceChildren(hoverTextP)
|
||||||
map.on("mousemove", e => hoverTextP.textContent = getIPAtMouse(e, false, false))
|
|
||||||
addControl(hoverTextControl, "bottom-left")
|
|
||||||
|
|
||||||
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), "top-left")
|
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
|
let curPopup
|
||||||
map.on("click", e => curPopup = new maplibregl.Popup().setHTML(getIPAtMouse(e, true, true)).setLngLat(e.lngLat).addTo(map))
|
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]
|
const main = document.getElementsByTagName("main")[0]
|
||||||
main.addEventListener("click", e => {
|
main.addEventListener("click", e => {
|
||||||
if (e.target === main && curPopup) curPopup.remove()
|
if (e.target === main && curPopup) curPopup.remove()
|
||||||
})
|
})
|
||||||
})
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
rangeInput.addEventListener("input", e => setJumpParam(rangeInput.value))
|
||||||
|
rangeInput.addEventListener("keydown", e => e.key === "Enter" && jump(rangeInput.value))
|
||||||
|
document.getElementById("range-button").addEventListener("click", () => jump(rangeInput.value))
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue