update webapp for isp recognition
This commit is contained in:
@@ -127,3 +127,43 @@ INSERT INTO geofeed_settings (setting_key, setting_value) VALUES
|
||||
('ipregistry_api_key', ''),
|
||||
('ipregistry_enabled', '0')
|
||||
ON DUPLICATE KEY UPDATE setting_key = setting_key;
|
||||
|
||||
-- ============================================
|
||||
-- MIGRATION: Add columns for existing databases
|
||||
-- These statements safely add columns if they don't exist
|
||||
-- ============================================
|
||||
|
||||
-- Add sort_order column
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS sort_order INT DEFAULT 0 AFTER notes;
|
||||
|
||||
-- Add IP Registry enrichment columns
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_enriched_at TIMESTAMP NULL DEFAULT NULL AFTER sort_order;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_isp VARCHAR(255) DEFAULT NULL AFTER ipr_enriched_at;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_org VARCHAR(255) DEFAULT NULL AFTER ipr_isp;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_asn INT DEFAULT NULL AFTER ipr_org;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_asn_name VARCHAR(255) DEFAULT NULL AFTER ipr_asn;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_connection_type VARCHAR(50) DEFAULT NULL AFTER ipr_asn_name;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_country_name VARCHAR(100) DEFAULT NULL AFTER ipr_connection_type;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_region_name VARCHAR(100) DEFAULT NULL AFTER ipr_country_name;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_timezone VARCHAR(100) DEFAULT NULL AFTER ipr_region_name;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_latitude DECIMAL(10, 7) DEFAULT NULL AFTER ipr_timezone;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS ipr_longitude DECIMAL(10, 7) DEFAULT NULL AFTER ipr_latitude;
|
||||
|
||||
-- Add security flag columns
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_abuser TINYINT(1) DEFAULT 0 AFTER ipr_longitude;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_attacker TINYINT(1) DEFAULT 0 AFTER flag_abuser;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_bogon TINYINT(1) DEFAULT 0 AFTER flag_attacker;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_cloud_provider TINYINT(1) DEFAULT 0 AFTER flag_bogon;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_proxy TINYINT(1) DEFAULT 0 AFTER flag_cloud_provider;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_relay TINYINT(1) DEFAULT 0 AFTER flag_proxy;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_tor TINYINT(1) DEFAULT 0 AFTER flag_relay;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_tor_exit TINYINT(1) DEFAULT 0 AFTER flag_tor;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_vpn TINYINT(1) DEFAULT 0 AFTER flag_tor_exit;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_anonymous TINYINT(1) DEFAULT 0 AFTER flag_vpn;
|
||||
ALTER TABLE geofeed_entries ADD COLUMN IF NOT EXISTS flag_threat TINYINT(1) DEFAULT 0 AFTER flag_anonymous;
|
||||
|
||||
-- Add indexes if they don't exist (MariaDB 10.5+ supports IF NOT EXISTS for indexes)
|
||||
-- For older versions, these will show warnings but won't fail
|
||||
ALTER TABLE geofeed_entries ADD INDEX IF NOT EXISTS idx_sort_order (sort_order);
|
||||
ALTER TABLE geofeed_entries ADD INDEX IF NOT EXISTS idx_isp (ipr_isp);
|
||||
ALTER TABLE geofeed_entries ADD INDEX IF NOT EXISTS idx_asn (ipr_asn);
|
||||
|
||||
@@ -391,8 +391,12 @@ function requireAuthApi() {
|
||||
/**
|
||||
* Fetch IP data from ipregistry.co
|
||||
*/
|
||||
function fetchIpRegistryData($ipPrefix) {
|
||||
function fetchIpRegistryData($ipPrefix, $db = null) {
|
||||
// Try environment variable first, then database setting
|
||||
$apiKey = IPREGISTRY_API_KEY;
|
||||
if (empty($apiKey) && $db !== null) {
|
||||
$apiKey = getSetting($db, 'ipregistry_api_key', '');
|
||||
}
|
||||
if (empty($apiKey)) {
|
||||
return ['success' => false, 'error' => 'IP Registry API key not configured'];
|
||||
}
|
||||
@@ -439,11 +443,12 @@ function fetchIpRegistryData($ipPrefix) {
|
||||
}
|
||||
|
||||
// Extract relevant fields
|
||||
// Note: ipregistry.co doesn't have a direct 'isp' field - organization is the ISP/org name
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'ipr_isp' => $data['connection']['isp'] ?? null,
|
||||
'ipr_org' => $data['connection']['organization'] ?? null,
|
||||
'ipr_isp' => $data['connection']['organization'] ?? null,
|
||||
'ipr_org' => $data['company']['name'] ?? $data['connection']['organization'] ?? null,
|
||||
'ipr_asn' => $data['connection']['asn'] ?? null,
|
||||
'ipr_asn_name' => $data['connection']['domain'] ?? null,
|
||||
'ipr_connection_type' => $data['connection']['type'] ?? null,
|
||||
@@ -472,7 +477,7 @@ function fetchIpRegistryData($ipPrefix) {
|
||||
* Enrich IP entry with IP Registry data
|
||||
*/
|
||||
function enrichIpEntry($db, $entryId, $ipPrefix) {
|
||||
$result = fetchIpRegistryData($ipPrefix);
|
||||
$result = fetchIpRegistryData($ipPrefix, $db);
|
||||
|
||||
if (!$result['success']) {
|
||||
return $result;
|
||||
|
||||
141
webapp/index.php
141
webapp/index.php
@@ -1941,6 +1941,30 @@ if (function_exists('requireAuth')) {
|
||||
<label class="form-label">Notes</label>
|
||||
<input type="text" class="form-input" id="notes" name="notes" placeholder="Optional notes for internal use">
|
||||
</div>
|
||||
|
||||
<!-- IP Enrichment Section (shown only when editing) -->
|
||||
<div id="enrichmentSection" style="display: none; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border-color);">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
|
||||
<label class="form-label" style="margin-bottom: 0;">IP Registry Enrichment</label>
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="reEnrichBtn" onclick="reEnrichCurrentEntry()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M23 4v6h-6M1 20v-6h6"/>
|
||||
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
|
||||
</svg>
|
||||
<span id="reEnrichBtnText">Re-enrich IP</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="enrichmentStatus" class="form-hint" style="font-size: 12px;"></div>
|
||||
<div id="enrichmentData" style="display: none; margin-top: 8px; font-size: 12px; color: var(--text-secondary);">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px 16px;">
|
||||
<div><strong>ISP:</strong> <span id="enrichIsp">-</span></div>
|
||||
<div><strong>ASN:</strong> <span id="enrichAsn">-</span></div>
|
||||
<div><strong>Org:</strong> <span id="enrichOrg">-</span></div>
|
||||
<div><strong>Type:</strong> <span id="enrichType">-</span></div>
|
||||
</div>
|
||||
<div id="enrichFlags" style="margin-top: 8px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeModal()">Cancel</button>
|
||||
@@ -2680,6 +2704,9 @@ if (function_exists('requireAuth')) {
|
||||
const modal = document.getElementById('entryModal');
|
||||
const form = document.getElementById('entryForm');
|
||||
const title = document.getElementById('modalTitle');
|
||||
const enrichSection = document.getElementById('enrichmentSection');
|
||||
const enrichStatus = document.getElementById('enrichmentStatus');
|
||||
const enrichData = document.getElementById('enrichmentData');
|
||||
|
||||
form.reset();
|
||||
|
||||
@@ -2693,9 +2720,53 @@ if (function_exists('requireAuth')) {
|
||||
document.getElementById('postalCode').value = entry.postal_code || '';
|
||||
document.getElementById('clientShortName').value = entry.client_short_name || '';
|
||||
document.getElementById('notes').value = entry.notes || '';
|
||||
|
||||
// Show enrichment section when editing
|
||||
enrichSection.style.display = 'block';
|
||||
|
||||
if (entry.ipr_enriched_at) {
|
||||
const enrichedDate = new Date(entry.ipr_enriched_at).toLocaleString();
|
||||
enrichStatus.innerHTML = `<span style="color: var(--success);">Enriched on ${enrichedDate}</span>`;
|
||||
enrichData.style.display = 'block';
|
||||
|
||||
// Populate enrichment data
|
||||
document.getElementById('enrichIsp').textContent = entry.ipr_isp || '-';
|
||||
document.getElementById('enrichAsn').textContent = entry.ipr_asn ? `AS${entry.ipr_asn}` : '-';
|
||||
document.getElementById('enrichOrg').textContent = entry.ipr_org || '-';
|
||||
document.getElementById('enrichType').textContent = entry.ipr_connection_type || '-';
|
||||
|
||||
// Show active flags
|
||||
const flags = [];
|
||||
if (entry.flag_abuser == 1) flags.push('Abuser');
|
||||
if (entry.flag_attacker == 1) flags.push('Attacker');
|
||||
if (entry.flag_bogon == 1) flags.push('Bogon');
|
||||
if (entry.flag_cloud_provider == 1) flags.push('Cloud');
|
||||
if (entry.flag_proxy == 1) flags.push('Proxy');
|
||||
if (entry.flag_relay == 1) flags.push('Relay');
|
||||
if (entry.flag_tor == 1) flags.push('Tor');
|
||||
if (entry.flag_tor_exit == 1) flags.push('Tor Exit');
|
||||
if (entry.flag_vpn == 1) flags.push('VPN');
|
||||
if (entry.flag_anonymous == 1) flags.push('Anonymous');
|
||||
if (entry.flag_threat == 1) flags.push('Threat');
|
||||
|
||||
const flagsEl = document.getElementById('enrichFlags');
|
||||
if (flags.length > 0) {
|
||||
flagsEl.innerHTML = '<strong>Flags:</strong> ' + flags.map(f => `<span style="background: var(--danger); color: white; padding: 1px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;">${f}</span>`).join('');
|
||||
} else {
|
||||
flagsEl.innerHTML = '<strong>Flags:</strong> <span style="color: var(--success);">None</span>';
|
||||
}
|
||||
} else {
|
||||
enrichStatus.innerHTML = '<span style="color: var(--text-tertiary);">Not enriched yet</span>';
|
||||
enrichData.style.display = 'none';
|
||||
}
|
||||
|
||||
// Reset button state
|
||||
document.getElementById('reEnrichBtn').disabled = false;
|
||||
document.getElementById('reEnrichBtnText').textContent = 'Re-enrich IP';
|
||||
} else {
|
||||
title.textContent = 'Add Entry';
|
||||
document.getElementById('entryId').value = '';
|
||||
enrichSection.style.display = 'none';
|
||||
}
|
||||
|
||||
modal.classList.add('active');
|
||||
@@ -3062,7 +3133,7 @@ if (function_exists('requireAuth')) {
|
||||
try {
|
||||
const result = await api('enrich_all', {}, 'POST', {});
|
||||
if (result.success) {
|
||||
showToast(`Enriched ${result.enriched} IPs. ${result.pending_enrichment || 0} remaining.`, 'success');
|
||||
showToast(`Enriched ${result.enriched} IPs. ${result.failed || 0} failed.`, 'success');
|
||||
loadEntries(currentPage);
|
||||
} else {
|
||||
showToast(result.error || 'Failed to enrich IPs', 'error');
|
||||
@@ -3075,6 +3146,74 @@ if (function_exists('requireAuth')) {
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enrich current entry from edit modal
|
||||
async function reEnrichCurrentEntry() {
|
||||
const id = document.getElementById('entryId').value;
|
||||
if (!id) return;
|
||||
|
||||
const btn = document.getElementById('reEnrichBtn');
|
||||
const btnText = document.getElementById('reEnrichBtnText');
|
||||
|
||||
btn.disabled = true;
|
||||
btnText.textContent = 'Enriching...';
|
||||
|
||||
try {
|
||||
const result = await api('enrich_ip', {}, 'POST', { id: parseInt(id) });
|
||||
if (result.success) {
|
||||
showToast('IP enriched successfully', 'success');
|
||||
|
||||
// Reload the entry data to update the modal
|
||||
const entryResult = await api('get', { id });
|
||||
if (entryResult.success) {
|
||||
// Update enrichment display without closing modal
|
||||
const entry = entryResult.data;
|
||||
const enrichStatus = document.getElementById('enrichmentStatus');
|
||||
const enrichData = document.getElementById('enrichmentData');
|
||||
|
||||
const enrichedDate = new Date(entry.ipr_enriched_at).toLocaleString();
|
||||
enrichStatus.innerHTML = `<span style="color: var(--success);">Enriched on ${enrichedDate}</span>`;
|
||||
enrichData.style.display = 'block';
|
||||
|
||||
document.getElementById('enrichIsp').textContent = entry.ipr_isp || '-';
|
||||
document.getElementById('enrichAsn').textContent = entry.ipr_asn ? `AS${entry.ipr_asn}` : '-';
|
||||
document.getElementById('enrichOrg').textContent = entry.ipr_org || '-';
|
||||
document.getElementById('enrichType').textContent = entry.ipr_connection_type || '-';
|
||||
|
||||
// Update flags
|
||||
const flags = [];
|
||||
if (entry.flag_abuser == 1) flags.push('Abuser');
|
||||
if (entry.flag_attacker == 1) flags.push('Attacker');
|
||||
if (entry.flag_bogon == 1) flags.push('Bogon');
|
||||
if (entry.flag_cloud_provider == 1) flags.push('Cloud');
|
||||
if (entry.flag_proxy == 1) flags.push('Proxy');
|
||||
if (entry.flag_relay == 1) flags.push('Relay');
|
||||
if (entry.flag_tor == 1) flags.push('Tor');
|
||||
if (entry.flag_tor_exit == 1) flags.push('Tor Exit');
|
||||
if (entry.flag_vpn == 1) flags.push('VPN');
|
||||
if (entry.flag_anonymous == 1) flags.push('Anonymous');
|
||||
if (entry.flag_threat == 1) flags.push('Threat');
|
||||
|
||||
const flagsEl = document.getElementById('enrichFlags');
|
||||
if (flags.length > 0) {
|
||||
flagsEl.innerHTML = '<strong>Flags:</strong> ' + flags.map(f => `<span style="background: var(--danger); color: white; padding: 1px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;">${f}</span>`).join('');
|
||||
} else {
|
||||
flagsEl.innerHTML = '<strong>Flags:</strong> <span style="color: var(--success);">None</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Also refresh the main table
|
||||
loadEntries(currentPage);
|
||||
} else {
|
||||
showToast(result.error || 'Failed to enrich IP', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Network error', 'error');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btnText.textContent = 'Re-enrich IP';
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function
|
||||
async function logout() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user