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 mapStatus = $state<string | null>(null);
|
||||||
let renderAttempts = $state(0);
|
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() {
|
async function ensureLeaflet() {
|
||||||
if (leaflet) return;
|
if (leaflet) return;
|
||||||
leaflet = await import('leaflet');
|
leaflet = await import('leaflet');
|
||||||
@@ -77,9 +152,11 @@
|
|||||||
lat: number;
|
lat: number;
|
||||||
lon: number;
|
lon: number;
|
||||||
count: number;
|
count: number;
|
||||||
|
category: IpCategory;
|
||||||
country_code?: string | null;
|
country_code?: string | null;
|
||||||
city_name?: string | null;
|
city_name?: string | null;
|
||||||
asn?: number | null;
|
asn?: number | null;
|
||||||
|
as_organization?: string | null;
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
@@ -97,6 +174,8 @@
|
|||||||
const lonG = Math.round(lon * 10) / 10;
|
const lonG = Math.round(lon * 10) / 10;
|
||||||
const country_code = ip.country_code ?? null;
|
const country_code = ip.country_code ?? null;
|
||||||
const city_name = ip.city_name ?? null;
|
const city_name = ip.city_name ?? null;
|
||||||
|
const asOrg = ip.as_organization ?? null;
|
||||||
|
const category = classifyByAsOrg(asOrg);
|
||||||
|
|
||||||
// Ключ включает country/city, чтобы не смешивать разные локации
|
// Ключ включает country/city, чтобы не смешивать разные локации
|
||||||
// при одинаковых координатах округления.
|
// при одинаковых координатах округления.
|
||||||
@@ -104,6 +183,11 @@
|
|||||||
const item = groups.get(key);
|
const item = groups.get(key);
|
||||||
if (item) {
|
if (item) {
|
||||||
item.count++;
|
item.count++;
|
||||||
|
if (categoryPriority(category) > categoryPriority(item.category)) {
|
||||||
|
item.category = category;
|
||||||
|
item.asn = ip.asn ?? null;
|
||||||
|
item.as_organization = asOrg;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +195,11 @@
|
|||||||
lat: latG,
|
lat: latG,
|
||||||
lon: lonG,
|
lon: lonG,
|
||||||
count: 1,
|
count: 1,
|
||||||
|
category,
|
||||||
country_code,
|
country_code,
|
||||||
city_name,
|
city_name,
|
||||||
asn: ip.asn ?? null
|
asn: ip.asn ?? null,
|
||||||
|
as_organization: asOrg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,12 +227,20 @@
|
|||||||
for (const p of points) {
|
for (const p of points) {
|
||||||
const r = Math.min(28, 5 + Math.log10(p.count + 1) * 9);
|
const r = Math.min(28, 5 + Math.log10(p.count + 1) * 9);
|
||||||
const color =
|
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 = [
|
const tooltipParts = [
|
||||||
`${p.count} IP`,
|
`${p.count} IP`,
|
||||||
|
categoryLabel(p.category),
|
||||||
p.country_code ? p.country_code : '—',
|
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);
|
].filter(Boolean);
|
||||||
|
|
||||||
const marker = leaflet.circleMarker([p.lat, p.lon], {
|
const marker = leaflet.circleMarker([p.lat, p.lon], {
|
||||||
|
|||||||
Reference in New Issue
Block a user