NewHome/assets/js/ip.js

290 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ip.js —— IP 地址处理
* 提取输入文本中全部 IPv4 → 按 /24 段聚合输出(文本 / 高亮预览):
* IP段 发现数量(段内去重IP数) IP段性质
* 1.2.3.0/24 8 公网
* 1.2.3.4
*
* 「重点匹配 IP」填写的 IP 命中后,其所在 /24 段优先置顶、对应 IP 红色标出。
* CDN 段库由服务端通过 #ipCdnRaw 隐藏文本框注入,可在后台维护。
*/
(function () {
'use strict';
function $(id) { return document.getElementById(id); }
// 内网 / 保留地址段IPv4
var PRIV_RANGES = [
['0.0.0.0', 8], ['10.0.0.0', 8], ['100.64.0.0', 10], ['127.0.0.0', 8],
['169.254.0.0', 16], ['172.16.0.0', 12], ['192.168.0.0', 16],
['192.0.2.0', 24], ['198.51.100.0', 24], ['203.0.113.0', 24],
['224.0.0.0', 4], ['240.0.0.0', 4]
].map(function (r) {
return { net: ipToInt(r[0]), prefix: r[1] };
});
function ipToInt(ip) {
var p = String(ip).split('.');
return (((+p[0]) << 24) + ((+p[1]) << 16) + ((+p[2]) << 8) + (+p[3])) >>> 0;
}
function inRange(ipInt, net, prefix) {
var mask = (prefix === 0) ? 0 : ((0xFFFFFFFF << (32 - prefix)) >>> 0);
return ((ipInt & mask) >>> 0) === ((net & mask) >>> 0);
}
function isPrivate(ipInt) {
for (var i = 0; i < PRIV_RANGES.length; i++) {
if (inRange(ipInt, PRIV_RANGES[i].net, PRIV_RANGES[i].prefix)) return true;
}
return false;
}
/* ---------- CDN 段库解析 ---------- */
function parseCdnText(text) {
var out = [], lines = String(text || '').split(/\r?\n/), i, m, p;
for (i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (!line || line.charAt(0) === '#') continue;
p = line.split(/\s+/);
m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/.exec(p[0]);
if (!m) continue;
var a = +m[1], b = +m[2], c = +m[3], d = +m[4], pre = +m[5];
if (a > 255 || b > 255 || c > 255 || d > 255 || pre > 32) continue;
out.push({
net: (((a << 24) + (b << 16) + (c << 8) + d) >>> 0),
prefix: pre,
label: p.slice(1).join(' ') || 'CDN'
});
}
return out;
}
function cdnOf(ipInt, ranges) {
for (var i = 0; i < ranges.length; i++) {
if (inRange(ipInt, ranges[i].net, ranges[i].prefix)) return ranges[i].label;
}
return null;
}
/* ---------- IPv4 提取 ---------- */
function extractIps(text) {
var re = /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/g, m, found = [];
while ((m = re.exec(text)) !== null) {
var a = +m[1], b = +m[2], c = +m[3], d = +m[4];
if (a > 255 || b > 255 || c > 255 || d > 255) continue;
found.push(a + '.' + b + '.' + c + '.' + d);
}
return found;
}
/* ---------- 重点 IP 解析 ---------- */
function parseFocus(text) {
var set = {};
var parts = String(text || '').split(/[\s,;]+/);
for (var i = 0; i < parts.length; i++) {
var p = parts[i].trim();
if (!p) continue;
var m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(p);
if (!m) continue;
if (+m[1] > 255 || +m[2] > 255 || +m[3] > 255 || +m[4] > 255) continue;
set[m[1] + '.' + m[2] + '.' + m[3] + '.' + m[4]] = true;
}
return set;
}
function ipLess(a, b) {
var aa = a.split('.').map(Number), bb = b.split('.').map(Number), k;
for (k = 0; k < 4; k++) {
if (aa[k] !== bb[k]) return aa[k] - bb[k];
}
return 0;
}
function keyLess(a, b) {
var aa = a.split('.').map(Number), bb = b.split('.').map(Number), k;
for (k = 0; k < 3; k++) {
if (aa[k] !== bb[k]) return aa[k] - bb[k];
}
return 0;
}
var TYPE_ZH = { priv: '内网', cdn: 'CDN', pub: '公网' };
/* 返回 { segs:[{line, hit, ips:[{ip,hit}]}], segTotals, totalIps, totalSeg, hitSeg, hitIps, rangeCount } */
function analyze(text, cdnRaw, focusSet) {
var all = extractIps(text);
if (!all.length) return null;
var ranges = parseCdnText(cdnRaw);
var segMap = {}, segOrder = [];
for (var i = 0; i < all.length; i++) {
var ip = all[i];
var key = ip.substring(0, ip.lastIndexOf('.'));
if (!segMap[key]) {
segMap[key] = { key: key, ips: [], classes: {} };
segOrder.push(key);
}
var seg = segMap[key];
if (seg.ips.indexOf(ip) === -1) {
seg.ips.push(ip);
var int = ipToInt(ip);
var vendor = cdnOf(int, ranges);
var cls = vendor ? 'cdn' : (isPrivate(int) ? 'priv' : 'pub');
seg.classes[cls] = (seg.classes[cls] || 0) + 1;
}
}
segOrder.sort(keyLess);
function segLabel(s) {
var keys = Object.keys(s.classes);
if (keys.length === 1) return TYPE_ZH[keys[0]];
var best = keys.slice().sort(function (x, y) {
return (s.classes[y] - s.classes[x]) || (TYPE_ZH[x] < TYPE_ZH[y] ? -1 : 1);
})[0];
return TYPE_ZH[best] + '为主(混合)';
}
var segs = segOrder.map(function (k) { return segMap[k]; });
segs.forEach(function (s) { s.label = segLabel(s); });
// 命中重点 IP 的段排前,组内段号升序
var focusSegs = [], otherSegs = [];
segs.forEach(function (s) {
s.hit = s.ips.some(function (ip) { return focusSet[ip]; });
(s.hit ? focusSegs : otherSegs).push(s);
});
segs = focusSegs.concat(otherSegs);
var totalIps = 0, hitIps = 0, hitSeg = 0;
segs.forEach(function (s) {
totalIps += s.ips.length;
if (s.hit) hitSeg++;
s.ips.sort(ipLess);
var h = [], o = [];
s.ips.forEach(function (ip) {
if (focusSet[ip]) { h.push(ip); hitIps++; }
else o.push(ip);
});
s.details = h.concat(o);
s.line = s.key + '.0/24\t' + s.ips.length + '\t' + s.label;
});
var cntSeg = { priv: 0, cdn: 0, pub: 0 };
segs.forEach(function (s) {
var lab = s.label;
if (lab.indexOf('内网') === 0) cntSeg.priv++;
else if (lab.indexOf('CDN') === 0) cntSeg.cdn++;
else cntSeg.pub++;
});
return {
segs: segs,
totalIps: totalIps,
totalSeg: segs.length,
hitSeg: hitSeg,
hitIps: hitIps,
cntSeg: cntSeg,
rangeCount: ranges.length
};
}
/* ---------- 文本 / 高亮输出 ---------- */
function esc(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function plainText(data) {
var lines = ['IP段\t发现数量\tIP段性质'];
data.segs.forEach(function (s) {
lines.push(s.line);
s.details.forEach(function (ip) { lines.push('\t' + ip); });
lines.push('');
});
return lines.join('\n');
}
function previewHtml(data) {
var parts = [];
parts.push(esc('IP段\t发现数量\tIP段性质').replace(/\t/g, ' '));
data.segs.forEach(function (s) {
var lineTxt = s.line.replace(/\t/g, ' ');
parts.push(s.hit ? '<span class="ip-seg-hit">★ ' + esc(lineTxt) + '</span>' : esc(lineTxt));
s.details.forEach(function (ip) {
parts.push(focusCur[ip] ? '<span class="ip-hit">' + esc(ip) + '</span>' : esc(ip));
});
parts.push('');
});
return parts.join('\n');
}
var focusCur = {};
/* ---------- 事件 ---------- */
var lastData = null;
function run() {
var input = $('ipInput');
var msg = $('ipMsg');
msg.innerHTML = '';
var text = input.value;
if (!text.trim()) {
msg.innerHTML = '<div class="err-msg">请先粘贴要分析的日志 / 文本内容。</div>';
return;
}
var cdnRaw = ($('ipCdnRaw') && $('ipCdnRaw').value) || '';
var fs = parseFocus($('ipFocus').value);
focusCur = fs;
lastData = analyze(text, cdnRaw, fs);
if (!lastData) {
$('ipResult').hidden = true;
msg.innerHTML = '<div class="err-msg">未在输入内容中解析到有效的 IPv4 地址。</div>';
return;
}
var d = lastData;
$('ipPreview').innerHTML = previewHtml(d);
var st = d.cntSeg;
var note = '共提取去重 IP ' + d.totalIps + ' 个,涉及 ' + d.totalSeg + ' 个 /24 段(内网 ' +
st.priv + ' 段 / CDN ' + st.cdn + ' 段 / 公网 ' + st.pub + ' 段)。';
if (Object.keys(parseFocus($('ipFocus').value)).length === 0) {
note += ' 未填写重点 IP按原顺序输出。';
} else if (d.hitIps === 0) {
note += ' 填写的重点 IP 均未在输入内容中命中。';
} else {
note += ' 命中重点 IP ' + d.hitIps + ' 个,' + d.hitSeg + ' 个 /24 段已优先置顶。';
}
if (d.rangeCount === 0) note += ' CDN 段库为空,可在后台补充。';
$('ipStat').textContent = note;
$('ipResult').hidden = false;
msg.innerHTML = '';
}
function copyText(text) {
function done(ok) {
var btn = $('ipCopy');
var old = btn.textContent;
btn.textContent = ok ? '✓ 已复制' : '复制失败';
setTimeout(function () { btn.textContent = old; }, 1200);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function () { done(true); }, function () { fallback(text, done); });
} else {
fallback(text, done);
}
}
function fallback(text, done) {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.cssText = 'position:fixed;opacity:0;';
document.body.appendChild(ta);
ta.select();
try { done(document.execCommand('copy')); } catch (e) { done(false); }
document.body.removeChild(ta);
}
function clearAll() {
$('ipInput').value = '';
$('ipFocus').value = '';
$('ipMsg').innerHTML = '';
$('ipResult').hidden = true;
$('ipInput').focus();
}
$('ipBtn').addEventListener('click', run);
$('ipCopy').addEventListener('click', function () {
if (lastData) copyText(plainText(lastData));
});
$('ipClear').addEventListener('click', clearAll);
$('ipInput').addEventListener('keydown', function (ev) {
if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey)) run();
});
})();