IPAM: IPv6-tuki + subnetin käyttöaste-laskuri
- parseNetwork() ja isSubnetOf() tukevat nyt IPv4 ja IPv6 (BigInt) - IPv6 verkot (esim. 2001:db8::/32) sijoittuvat puuhierarkiaan - Subnetin kohdalla näytetään käyttöaste: esim. "3/256" kertoo kuinka monta alisubnettia on käytössä vs kapasiteetti - Ei-parsittavat osoitteet näytetään puun juuressa Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
104
script.js
104
script.js
@@ -2973,52 +2973,100 @@ let ipamData = [];
|
||||
let ipamExpandedIds = new Set();
|
||||
let ipamDrillStack = []; // [{id, label}] breadcrumb
|
||||
|
||||
// --- IP-laskenta-apufunktiot ---
|
||||
function ipToInt(ip) {
|
||||
return ip.split('.').reduce((acc, oct) => (acc << 8) + parseInt(oct), 0) >>> 0;
|
||||
// --- IP-laskenta-apufunktiot (IPv4 + IPv6) ---
|
||||
function ipv4ToBI(ip) {
|
||||
return ip.split('.').reduce((acc, oct) => (acc << 8n) + BigInt(parseInt(oct)), 0n);
|
||||
}
|
||||
function prefixToMask(prefix) {
|
||||
return prefix === 0 ? 0 : (~0 << (32 - prefix)) >>> 0;
|
||||
function ipv6ToBI(ip) {
|
||||
// Expand :: shorthand
|
||||
let parts = ip.split('::');
|
||||
let left = parts[0] ? parts[0].split(':') : [];
|
||||
let right = parts.length > 1 && parts[1] ? parts[1].split(':') : [];
|
||||
const missing = 8 - left.length - right.length;
|
||||
const full = [...left, ...Array(missing).fill('0'), ...right];
|
||||
return full.reduce((acc, hex) => (acc << 16n) + BigInt(parseInt(hex || '0', 16)), 0n);
|
||||
}
|
||||
function parseNetwork(verkko) {
|
||||
if (!verkko) return null;
|
||||
const v = verkko.trim();
|
||||
let ip, prefix;
|
||||
if (v.includes('/')) {
|
||||
const [ip, pfx] = v.split('/');
|
||||
const prefix = parseInt(pfx);
|
||||
if (isNaN(prefix) || prefix < 0 || prefix > 32) return null;
|
||||
const slash = v.lastIndexOf('/');
|
||||
ip = v.substring(0, slash);
|
||||
prefix = parseInt(v.substring(slash + 1));
|
||||
if (isNaN(prefix) || prefix < 0) return null;
|
||||
} else {
|
||||
ip = v;
|
||||
prefix = null; // auto-detect
|
||||
}
|
||||
// IPv6?
|
||||
if (ip.includes(':')) {
|
||||
const maxBits = 128;
|
||||
if (prefix === null) prefix = 128;
|
||||
if (prefix > maxBits) return null;
|
||||
try {
|
||||
return { net: ipv6ToBI(ip), prefix, bits: maxBits, v6: true };
|
||||
} catch { return null; }
|
||||
}
|
||||
// IPv4
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) return null;
|
||||
return { net: ipToInt(ip), prefix };
|
||||
} else {
|
||||
const parts = v.split('.');
|
||||
if (parts.length !== 4) return null;
|
||||
return { net: ipToInt(v), prefix: 32 };
|
||||
}
|
||||
const maxBits = 32;
|
||||
if (prefix === null) prefix = 32;
|
||||
if (prefix > maxBits) return null;
|
||||
return { net: ipv4ToBI(ip), prefix, bits: maxBits, v6: false };
|
||||
}
|
||||
function isSubnetOf(childNet, childPrefix, parentNet, parentPrefix) {
|
||||
function isSubnetOf(childNet, childPrefix, childBits, parentNet, parentPrefix, parentBits) {
|
||||
if (childBits !== parentBits) return false; // eri perhe (v4 vs v6)
|
||||
if (childPrefix <= parentPrefix) return false;
|
||||
const mask = prefixToMask(parentPrefix);
|
||||
return (childNet & mask) === (parentNet & mask);
|
||||
const shift = BigInt(parentBits - parentPrefix);
|
||||
return (childNet >> shift) === (parentNet >> shift);
|
||||
}
|
||||
// Laske subnetin käyttöaste: kuinka monta lasta (direct children) vs kapasiteetti
|
||||
function subnetUsageHtml(node) {
|
||||
if (node.entry.tyyppi !== 'subnet' || node.children.length === 0) return '';
|
||||
const childCount = node.children.length;
|
||||
// Laske kuinka monta "slottia" tässä subnetissa on seuraavalla tasolla
|
||||
// Etsi yleisin lapsi-prefix
|
||||
const childPrefixes = node.children.filter(c => c.prefix > node.prefix).map(c => c.prefix);
|
||||
if (childPrefixes.length === 0) return `<span class="ipam-usage">${childCount}</span>`;
|
||||
// Käytä pienintä child-prefixiä (isoimpia aliverkkoja) kapasiteetin laskuun
|
||||
const commonPrefix = Math.min(...childPrefixes);
|
||||
const bits = node.entry.tyyppi === 'subnet' ? (node.children[0]?.bits || 32) : 32;
|
||||
const slotBits = commonPrefix - node.prefix;
|
||||
if (slotBits <= 0 || slotBits > 20) return `<span class="ipam-usage">${childCount}</span>`;
|
||||
const totalSlots = 1 << slotBits; // 2^slotBits
|
||||
const sameLevel = node.children.filter(c => c.prefix === commonPrefix).length;
|
||||
const freeSlots = totalSlots - sameLevel;
|
||||
return `<span class="ipam-usage" title="${sameLevel}/${totalSlots} /${commonPrefix} käytössä">${sameLevel}/${totalSlots}</span>`;
|
||||
}
|
||||
|
||||
// --- Puurakenne ---
|
||||
function buildIpamTree(entries) {
|
||||
// Parsitaan verkko-osoitteet ja suodatetaan validit
|
||||
const items = entries.map(e => {
|
||||
// Parsitaan verkko-osoitteet; ei-parsittavat lisätään juureen sellaisenaan
|
||||
const unparsed = [];
|
||||
const items = [];
|
||||
for (const e of entries) {
|
||||
const parsed = parseNetwork(e.verkko);
|
||||
return parsed ? { entry: e, net: parsed.net, prefix: parsed.prefix, children: [] } : null;
|
||||
}).filter(Boolean);
|
||||
// Järjestetään: pienin prefix (isoin verkko) ensin, sitten IP-osoitteen mukaan
|
||||
items.sort((a, b) => a.prefix - b.prefix || a.net - b.net);
|
||||
if (parsed) {
|
||||
items.push({ entry: e, net: parsed.net, prefix: parsed.prefix, bits: parsed.bits, v6: parsed.v6, children: [] });
|
||||
} else {
|
||||
unparsed.push({ entry: e, net: 0n, prefix: 0, bits: 0, v6: false, children: [] });
|
||||
}
|
||||
}
|
||||
// Järjestetään: v4 ennen v6, pienin prefix ensin, sitten osoitteen mukaan
|
||||
items.sort((a, b) => {
|
||||
if (a.v6 !== b.v6) return a.v6 ? 1 : -1;
|
||||
if (a.prefix !== b.prefix) return a.prefix - b.prefix;
|
||||
return a.net < b.net ? -1 : a.net > b.net ? 1 : 0;
|
||||
});
|
||||
const roots = [];
|
||||
for (const item of items) {
|
||||
let placed = false;
|
||||
// Etsi lähin parent (suurin prefix joka sisältää tämän)
|
||||
const findParent = (nodes) => {
|
||||
for (let i = nodes.length - 1; i >= 0; i--) {
|
||||
const node = nodes[i];
|
||||
if (isSubnetOf(item.net, item.prefix, node.net, node.prefix)) {
|
||||
if (isSubnetOf(item.net, item.prefix, item.bits, node.net, node.prefix, node.bits)) {
|
||||
// Tarkista ensin onko jokin lapsi tarkempi parent
|
||||
if (!findParent(node.children)) {
|
||||
node.children.push(item);
|
||||
@@ -3032,6 +3080,8 @@ function buildIpamTree(entries) {
|
||||
roots.push(item);
|
||||
}
|
||||
}
|
||||
// Lisää ei-parsittavat (esim. virheelliset osoitteet) juureen
|
||||
roots.push(...unparsed);
|
||||
return roots;
|
||||
}
|
||||
|
||||
@@ -3057,7 +3107,7 @@ function flattenTree(nodes, depth, drillId) {
|
||||
for (const node of list) {
|
||||
const hasChildren = node.children.length > 0;
|
||||
const expanded = ipamExpandedIds.has(node.entry.id);
|
||||
rows.push({ entry: node.entry, depth: d, hasChildren, expanded });
|
||||
rows.push({ entry: node.entry, depth: d, hasChildren, expanded, node });
|
||||
if (hasChildren && expanded) {
|
||||
render(node.children, d + 1);
|
||||
}
|
||||
@@ -3140,7 +3190,7 @@ function renderIpam() {
|
||||
<td style="padding-left:${indent}rem;white-space:nowrap;">
|
||||
${toggleIcon}${typeTag}
|
||||
</td>
|
||||
<td><code class="ipam-network">${esc(e.verkko || '-')}</code>${drillBtn}</td>
|
||||
<td><code class="ipam-network">${esc(e.verkko || '-')}</code>${drillBtn} ${subnetUsageHtml(r.node)}</td>
|
||||
<td>${esc(e.nimi || '-')}</td>
|
||||
<td>${vlanRefHtml(e.vlan_id)}</td>
|
||||
<td>${e.site_name ? esc(e.site_name) : '<span style="color:#ccc;">—</span>'}</td>
|
||||
|
||||
@@ -1089,6 +1089,7 @@ span.empty {
|
||||
.ipam-bc-link:hover { text-decoration:underline; }
|
||||
.ipam-bc-current { color:#333; font-weight:600; cursor:default; }
|
||||
.ipam-bc-current:hover { text-decoration:none; }
|
||||
.ipam-usage { display:inline-block; margin-left:0.5rem; padding:1px 6px; border-radius:8px; font-size:0.72rem; font-weight:600; background:#f0f4ff; color:#4338ca; }
|
||||
|
||||
/* Role badge */
|
||||
.role-badge {
|
||||
|
||||
Reference in New Issue
Block a user