Add IP classification and categorization logic
- Introduced functions to classify IP addresses into categories (VPN, Hoster, ISP) based on the organization name, enhancing data organization. - Implemented priority and labeling for categories to improve user interface clarity. - Updated the data structure to include category information for each IP, allowing for better visualization and differentiation on the map. - Enhanced tooltip display to show categorized information, improving user feedback and understanding of IP data.
This commit is contained in:
@@ -26,6 +26,81 @@
|
||||
let mapStatus = $state<string | null>(null);
|
||||
let renderAttempts = $state(0);
|
||||
|
||||
type IpCategory = 'vpn' | 'hoster' | 'isp' | 'unknown';
|
||||
|
||||
function classifyByAsOrg(asOrg: string | null | undefined): IpCategory {
|
||||
const s = (asOrg ?? '').toLowerCase();
|
||||
if (!s) return 'unknown';
|
||||
|
||||
// VPN heuristics from org name.
|
||||
if (
|
||||
s.includes('vpn') ||
|
||||
s.includes('mullvad') ||
|
||||
s.includes('nordvpn') ||
|
||||
s.includes('proton') ||
|
||||
s.includes('expressvpn') ||
|
||||
s.includes('surfshark') ||
|
||||
s.includes('cyberghost') ||
|
||||
s.includes('strongvpn') ||
|
||||
s.includes('ivpn') ||
|
||||
s.includes('private internet access') ||
|
||||
s.includes('pia ')
|
||||
) {
|
||||
return 'vpn';
|
||||
}
|
||||
|
||||
// Hosting / cloud heuristics from org name.
|
||||
if (
|
||||
s.includes('cloudflare') ||
|
||||
s.includes('akamai') ||
|
||||
s.includes('fastly') ||
|
||||
s.includes('ovh') ||
|
||||
s.includes('hetzner') ||
|
||||
s.includes('digitalocean') ||
|
||||
s.includes('linode') ||
|
||||
s.includes('vultr') ||
|
||||
s.includes('amazon') ||
|
||||
s.includes('aws') ||
|
||||
s.includes('google') ||
|
||||
s.includes('gcp') ||
|
||||
s.includes('microsoft') ||
|
||||
s.includes('azure') ||
|
||||
s.includes('oracle') ||
|
||||
s.includes('telegram') ||
|
||||
s.includes('hosting') ||
|
||||
s.includes('datacenter') ||
|
||||
s.includes('data center') ||
|
||||
s.includes('cdn') ||
|
||||
s.includes('edge') ||
|
||||
s.includes('compute')
|
||||
) {
|
||||
return 'hoster';
|
||||
}
|
||||
|
||||
// If it doesn't match above, assume ISP.
|
||||
return 'isp';
|
||||
}
|
||||
|
||||
function categoryPriority(c: IpCategory): number {
|
||||
if (c === 'vpn') return 3;
|
||||
if (c === 'hoster') return 2;
|
||||
if (c === 'isp') return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function categoryLabel(c: IpCategory): string {
|
||||
if (c === 'vpn') return 'VPN';
|
||||
if (c === 'hoster') return 'Hoster';
|
||||
if (c === 'isp') return 'ISP';
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
function truncate(s: string, n: number): string {
|
||||
const x = s ?? '';
|
||||
if (x.length <= n) return x;
|
||||
return `${x.slice(0, n - 1)}…`;
|
||||
}
|
||||
|
||||
async function ensureLeaflet() {
|
||||
if (leaflet) return;
|
||||
leaflet = await import('leaflet');
|
||||
@@ -77,9 +152,11 @@
|
||||
lat: number;
|
||||
lon: number;
|
||||
count: number;
|
||||
category: IpCategory;
|
||||
country_code?: string | null;
|
||||
city_name?: string | null;
|
||||
asn?: number | null;
|
||||
as_organization?: string | null;
|
||||
}
|
||||
>();
|
||||
|
||||
@@ -97,6 +174,8 @@
|
||||
const lonG = Math.round(lon * 10) / 10;
|
||||
const country_code = ip.country_code ?? null;
|
||||
const city_name = ip.city_name ?? null;
|
||||
const asOrg = ip.as_organization ?? null;
|
||||
const category = classifyByAsOrg(asOrg);
|
||||
|
||||
// Ключ включает country/city, чтобы не смешивать разные локации
|
||||
// при одинаковых координатах округления.
|
||||
@@ -104,6 +183,11 @@
|
||||
const item = groups.get(key);
|
||||
if (item) {
|
||||
item.count++;
|
||||
if (categoryPriority(category) > categoryPriority(item.category)) {
|
||||
item.category = category;
|
||||
item.asn = ip.asn ?? null;
|
||||
item.as_organization = asOrg;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -111,9 +195,11 @@
|
||||
lat: latG,
|
||||
lon: lonG,
|
||||
count: 1,
|
||||
category,
|
||||
country_code,
|
||||
city_name,
|
||||
asn: ip.asn ?? null
|
||||
asn: ip.asn ?? null,
|
||||
as_organization: asOrg
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -141,12 +227,20 @@
|
||||
for (const p of points) {
|
||||
const r = Math.min(28, 5 + Math.log10(p.count + 1) * 9);
|
||||
const color =
|
||||
p.count >= 100 ? '#ef4444' : p.count >= 25 ? '#f97316' : p.count >= 5 ? '#22c55e' : '#60a5fa';
|
||||
p.category === 'vpn'
|
||||
? '#ef4444'
|
||||
: p.category === 'hoster'
|
||||
? '#f97316'
|
||||
: p.category === 'isp'
|
||||
? '#22c55e'
|
||||
: '#60a5fa';
|
||||
|
||||
const tooltipParts = [
|
||||
`${p.count} IP`,
|
||||
categoryLabel(p.category),
|
||||
p.country_code ? p.country_code : '—',
|
||||
p.city_name ? p.city_name : ''
|
||||
p.city_name ? p.city_name : '',
|
||||
p.asn != null ? `AS${p.asn} ${p.as_organization ? truncate(p.as_organization, 24) : ''}` : ''
|
||||
].filter(Boolean);
|
||||
|
||||
const marker = leaflet.circleMarker([p.lat, p.lon], {
|
||||
|
||||
Reference in New Issue
Block a user