### Setup Working Directory and Serve Report in Python
Source: https://context7.com/fastah/ip-geofeed-skills/llms.txt
Configures cross-platform working directories for generated files and reports using `pathlib`. It also starts a local HTTP server to serve the generated HTML report and opens it in the default web browser.
```python
from pathlib import Path
# Resolve FASTAH_WORK_DIR (cross-platform)
FASTAH_WORK_DIR = Path.home() / "fastah"
FASTAH_DATA_DIR = FASTAH_WORK_DIR / "data"
FASTAH_REPORT_DIR = FASTAH_WORK_DIR / "report"
# Create directories
FASTAH_DATA_DIR.mkdir(parents=True, exist_ok=True)
FASTAH_REPORT_DIR.mkdir(parents=True, exist_ok=True)
# File locations for pipeline outputs
REPORT_DATA_FILE = FASTAH_DATA_DIR / "report-data.json"
COMMENTS_FILE = FASTAH_DATA_DIR / "comments.json"
MCP_PAYLOAD_FILE = FASTAH_DATA_DIR / "mcp-server-payload.json"
HTML_REPORT_FILE = FASTAH_REPORT_DIR / "geofeed-report.html"
# Serve report with local HTTP server
import subprocess
import webbrowser
# Start server in background
subprocess.Popen(['python3', '-m', 'http.server', '-d', str(FASTAH_REPORT_DIR), '4242'])
# Open in browser
webbrowser.open('http://127.0.0.1:4242/geofeed-report.html')
```
--------------------------------
### Start Local Python HTTP Server
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/SKILL.md
Starts a local Python HTTP server to serve the report directory on port 4242. This command should be run as a background process.
```bash
python3 -m http.server -d FASTAH_REPORT_DIR 4242
```
--------------------------------
### Examples of IP Geofeed Entries
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/references/rfc8805.txt
Provides concrete examples of IP Geofeed entries with different levels of geolocation detail.
```APIDOC
## Examples of IP Geofeed Entries
### Description
This section provides examples of IP Geofeed entries demonstrating various IP address formats and geolocation granularities.
### Example Entries
**Basic Examples**:
```
192.0.2.0/25,US,US-AL,,
192.0.2.5,US,US-AL,Alabaster,
192.0.2.128/25,PL,PL-MZ,,
2001:db8::/32,PL,,,
2001:db8:cafe::/48,PL,PL-MZ,,
```
**IETF Network Geolocation Examples (Singapore)**:
```
# IETF106 (Singapore) - November 2019 - Singapore, SG
130.129.0.0/16,SG,SG-01,Singapore,
2001:df8::/32,SG,SG-01,Singapore,
31.133.128.0/18,SG,SG-01,Singapore,
31.130.224.0/20,SG,SG-01,Singapore,
2001:67c:1230::/46,SG,SG-01,Singapore,
2001:67c:370::/48,SG,SG-01,Singapore,
```
**RIPE NCC Conference Network Examples (Rotterdam)**:
```
193.0.24.0/21,NL,NL-ZH,Rotterdam,
2001:67c:64::/48,NL,NL-ZH,Rotterdam,
```
**ICANN Portable Conference Network Examples (Marrakech)**:
```
199.91.192.0/21,MA,MA-07,Marrakech
2620:f:8000::/48,MA,MA-07,Marrakech
```
```
--------------------------------
### AI Skill Invocation Examples
Source: https://context7.com/fastah/ip-geofeed-skills/llms.txt
Examples of how to invoke the Geofeed Tuner AI skill using different platforms like GitHub Copilot, Amp Code CLI, and the Context7 marketplace.
```bash
# GitHub Copilot / Claude Code / Amp / ChatGPT Codex
Hey chat, help me get started with the "Geofeed Tuner" agent skill from the SKILL.md file! :)
```
```bash
# Amp Code CLI
amp skill add --overwrite https://github.com/fastah/ip-geofeed-skills.git
/skill geofeed-tuner
```
```bash
# Context7 marketplace
npx ctx7 skills search Geofeed Tuner for RFC 8805 CSV feeds
```
--------------------------------
### Illustrative MCP Server Request Example
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/SKILL.md
An illustrative example of data sent to the MCP server for processing. This structure includes rowKeys for mapping responses and country codes. Always defer to the schema returned by `tools/list` for authoritative field names and types.
```json
[
{"rowKey": "550e8400-...", "countryCode":"CA", ...},
{"rowKey": "690e9301-...", "countryCode":"ZZ", ...}
]
```
--------------------------------
### Fastah MCP Server Configuration Example
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/SKILL.md
An example configuration for the Fastah MCP server, specifying the HTTP type and the server URL. This configuration is used to establish communication with the MCP service for geofeed data processing.
```json
"fastahIpGeofeed": {
"type": "http",
"url": "https://mcp.fastah.ai/mcp"
}
```
--------------------------------
### Install and Invoke Geofeed Tuner via Amp
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/README.md
Commands to add the Geofeed Tuner skill to the Amp CLI and subsequently invoke it for feed tuning tasks.
```shell
amp skill add --overwrite https://github.com/fastah/ip-geofeed-skills.git
/skill geofeed-tuner
```
--------------------------------
### Comment Map JSON Example
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/SKILL.md
An example of the JSON structure used to store comments and blank lines from a CSV file, keyed by line number.
```json
{ "4": "# It's OK for small city states to leave state ISO2 code unspecified" }
```
--------------------------------
### RFC 8805 Geofeed CSV Format Example
Source: https://context7.com/fastah/ip-geofeed-skills/llms.txt
Example of the 5-column CSV format for IP geolocation feeds, including IP prefix, country code, region, city, and deprecated postal code. Comments are allowed and start with '#'.
```csv
# RFC 8805 Geofeed CSV format
# Columns: ip_prefix, alpha2code, region, city, postal_code (deprecated)
202.125.100.144/28,ID,,Jakarta,
2605:59c8:2700::/40,CA,CA-QC,"Montreal"
150.228.170.0/24,SG,SG-01,Singapore,
# Comments are allowed and start with #
2406:2d40:8100::/42,SG,SG,Singapore,
```
--------------------------------
### Validate ISO 3166 country and region codes
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/references/snippets-python3.md
Examples for loading ISO 3166-1 alpha-2 country codes and ISO 3166-2 region codes from JSON files into sets for efficient validation.
```python
import json
# Load ISO 3166-1 country codes
with open('assets/iso3166-1.json') as f:
data = json.load(f)
valid_countries = {c['alpha_2'] for c in data['3166-1']}
# Load ISO 3166-2 region codes
with open('assets/iso3166-2.json') as f:
data = json.load(f)
valid_regions = {r['code'] for r in data['3166-2']}
```
--------------------------------
### MCP Server Payload Example
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/SKILL.md
This JSON structure represents a payload for the MCP server, including a rowKey for matching responses and geographical data. Ensure the rowKey is a UUID and fields like countryCode, regionCode, and cityName are correctly formatted.
```json
[
{"rowKey": "550e8400-e29b-41d4-a716-446655440000", "countryCode":"CA","regionCode":"CA-ON","cityName":"Toronto"},
{"rowKey": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "countryCode":"IN","regionCode":"IN-KA","cityName":"Bangalore"},
{"rowKey": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "countryCode":"IN","regionCode":"IN-KA"}
]
```
--------------------------------
### Initialize Pagination and Event Listeners
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Sets up the initial pagination state, handles the 'select all' checkbox functionality, and configures expandable rows with event listeners for checkboxes and row clicks.
```javascript
window.addEventListener('DOMContentLoaded', function() {
allRows = Array.from(document.querySelectorAll('.expandable-row'));
initializePagination();
const selectAllCheckbox = document.getElementById('selectAll');
const rowCheckboxes = document.querySelectorAll('.row-checkbox');
selectAllCheckbox.addEventListener('change', function() {
rowCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
});
rowCheckboxes.forEach(checkbox => {
checkbox.addEventListener('click', function(e) {
e.stopPropagation();
});
checkbox.addEventListener('keydown', function(e) {
if (e.key === ' ' || e.key === 'Enter') {
e.stopPropagation();
}
});
checkbox.addEventListener('change', function(e) {
e.stopPropagation();
const allChecked = Array.from(rowCheckboxes).every(cb => cb.checked);
const noneChecked = Array.from(rowCheckboxes).every(cb => !cb.checked);
selectAllCheckbox.checked = allChecked;
selectAllCheckbox.indeterminate = !allChecked && !noneChecked;
});
});
const expandableRows = document.querySelectorAll('.expandable-row');
expandableRows.forEach(row => {
const hasError = row.getAttribute('data-has-error') === 'true';
const hasWarning = row.getAttribute('data-has-warning') === 'true';
const hasSuggestion = row.getAttribute('data-has-suggestion') === 'true';
if (hasError || hasWarning || hasSuggestion) {
let detailRow = row.nextElementSibling;
while (detailRow && detailRow.classList.contains('expand-details-row')) {
if (detailRow.classList.contains('issues-row')) {
detailRow.classList.add('show');
}
detailRow = detailRow.nextElementSibling;
}
}
});
});
```
--------------------------------
### Define FASTAH Working Directories
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/SKILL.md
Sets up the necessary working directories for Fastah data and reports. Ensures directories exist before use.
```python
from pathlib import Path
FASTAH_WORK_DIR = Path.home() / "fastah"
FASTAH_DATA_DIR = FASTAH_WORK_DIR / "data"
FASTAH_REPORT_DIR = FASTAH_WORK_DIR / "report"
FASTAH_DATA_DIR.mkdir(parents=True, exist_ok=True)
FASTAH_REPORT_DIR.mkdir(parents=True, exist_ok=True)
```
--------------------------------
### Open Report in Browser
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/SKILL.md
Opens the generated geofeed report in the system's default web browser. This is typically used after starting the local HTTP server.
```python
webbrowser.open("http://127.0.0.1:4242/geofeed-report.html")
```
--------------------------------
### Initialize Leaflet Map for Match
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Initializes a Leaflet map with location boundary and marker for a match. It sets up the map, adds a tile layer, draws a rectangle for the bounding box, and places a marker.
```javascript
function initializeMapForMatch(mapId, match) {
try {
const mapInstance = L.map(mapId, {
scrollWheelZoom: false,
dragging: true,
zoomControl: true
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap',
maxZoom: 19
}).addTo(mapInstance);
const bbox = match.boundingBox;
if (bbox && bbox.length === 4) {
const [west, north, east, south] = bbox;
const centerLat = (south + north) / 2;
const centerLng = (west + east) / 2;
const isPoint = (west === east && north === south);
if (isPoint) {
const offset = 0.05;
const bounds = [
[south - offset, west - offset],
[north + offset, east + offset]
];
L.rectangle(bounds, {
color: '#dc3545',
weight: 3,
opacity: 1,
fillColor: '#dc3545',
fillOpacity: 0.2
}).addTo(mapInstance);
mapInstance.fitBounds(bounds, {
padding: [30, 30]
});
} else {
const bounds = [
[south, west],
[north, east]
];
L.rectangle(bounds, {
color: '#dc3545',
weight: 3,
opacity: 1,
fillColor: '#dc3545',
fillOpacity: 0.2
}).addTo(mapInstance);
mapInstance.fitBounds(bounds, {
padding: [20, 20]
});
}
L.marker([centerLat, centerLng]).addTo(mapInstance)
.bindPopup(`${escapeHtml(match.placeName)}
${escapeHtml(match.stateCode)}`);
maps[mapId] = mapInstance;
setTimeout(() => {
mapInstance.invalidateSize();
}, TIMING.MAP_RESIZE_DELAY);
}
} catch (error) {
console.error('Error initializing map:', error);
}
}
```
--------------------------------
### Get Modal and Close Button Elements
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Retrieves references to the modal and its close button from the DOM. These elements are used to control modal visibility and handle user interactions.
```javascript
const modal = document.getElementById('locationModal');
const modalContent = document.querySelector('.modal-content');
const closeBtn = document.querySelector('.close');
```
--------------------------------
### Search for Geofeed Tuner via Context7
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/README.md
Uses the Context7 CLI to search for the Geofeed Tuner skill within the marketplace.
```shell
npx ctx7 skills search Geofeed Tuner for RFC 8805 CSV feeds
```
--------------------------------
### Fastah Place Search API
Source: https://context7.com/fastah/ip-geofeed-skills/llms.txt
Use the REST API or MCP tool to geocode place names and get tuning suggestions with bounding boxes and H3 cells.
```APIDOC
## Fastah Place Search API
Use the REST API or MCP tool to geocode place names and get tuning suggestions with bounding boxes and H3 cells.
### POST /rest/geofeeds/place-search
#### Description
Searches for place location data using the Fastah API and returns matches with bounding boxes and H3 cells.
#### Method
POST
#### Endpoint
`https://mcp.fastah.ai/rest/geofeeds/place-search`
#### Parameters
##### Request Body
- **rows** (array) - Required - A list of geofeed entries to search for.
- **rowKey** (string) - Required - A unique identifier for the row.
- **countryCode** (string) - Required - The ISO 3166-1 alpha-2 country code.
- **regionCode** (string) - Optional - The ISO 3166-2 region code (e.g., US-CA).
- **cityName** (string) - Optional - The name of the city.
- **searchMode** (string) - Optional - The search mode, defaults to 'auto'.
### Request Example
```json
{
"rows": [
{
"rowKey": "unique-id-123",
"countryCode": "CA",
"regionCode": "CA-ON",
"cityName": "Toronto",
"searchMode": "auto"
}
]
}
```
### Response
#### Success Response (200)
- **results** (array) - A list of search results.
- **rowKey** (string) - The unique identifier for the row.
- **matches** (array) - A list of matched places.
- **placeName** (string) - The name of the place.
- **countryCode** (string) - The country code.
- **stateCode** (string) - The state or region code.
- **placeType** (string) - The type of place (e.g., city).
- **boundingBox** (array) - The bounding box coordinates [minLon, minLat, maxLon, maxLat].
- **h3Cells** (array) - A list of H3 cell identifiers.
#### Response Example
```json
{
"results": [
{
"rowKey": "unique-id-123",
"matches": [
{
"placeName": "Toronto",
"countryCode": "CA",
"stateCode": "CA-ON",
"placeType": "city",
"boundingBox": [-79.64, 43.85, -79.12, 43.58],
"h3Cells": ["872a92d5bffffff", "872a92d59ffffff"]
}
]
}
]
}
```
### Batch Search
#### Description
Allows for batch geocoding of multiple geofeed entries in a single request (up to 1000 entries per request).
#### Method
POST
#### Endpoint
`https://mcp.fastah.ai/rest/geofeeds/place-search`
#### Parameters
##### Request Body
- **rows** (array) - Required - A list of geofeed entries to search for.
- **rowKey** (string) - Required - A unique identifier for the row.
- **countryCode** (string) - Required - The ISO 3166-1 alpha-2 country code.
- **regionCode** (string) - Optional - The ISO 3166-2 region code (e.g., US-CA).
- **cityName** (string) - Optional - The name of the city.
- **searchMode** (string) - Optional - The search mode, defaults to 'auto'.
### Request Example
```python
entries = [
{'countryCode': 'CA', 'regionCode': 'CA-ON', 'cityName': 'Toronto'},
{'countryCode': 'US', 'regionCode': 'US-CA', 'cityName': 'Los Angeles'}
]
# Assuming batch_search function is defined as in the provided text
# response = batch_search(entries)
```
```
--------------------------------
### Toggle Between Viewing and Tuning Modes
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Switches the application's mode between viewing and tuning. It checks the current body class to determine the mode and applies the appropriate classes.
```javascript
function toggleMode() {
const isTuning = document.body.classList.contains('tuning-mode');
setMode(isTuning ? 'viewing' : 'tuning');
}
```
--------------------------------
### Initialize Geofeed Tuner in AI Chat
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/README.md
A prompt to initiate the Geofeed Tuner skill within an AI coding assistant environment. This command instructs the agent to read the SKILL.md file and begin the tuning process.
```text
Hey chat, help me get started with the "Geofeed Tuner" agent skill from the SKILL.md file! :)
```
--------------------------------
### Handle Tune Button Click
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Initiates fetching location matches when a tune button is clicked. It identifies selected issues and prepares data for the API call.
```javascript
async function handleTuneButtonClick(button) {
currentTuningButton = button;
const issuesContainer = button.closest('td');
const checkedBoxes = issuesContainer.querySelectorAll('.message-line input[type="checkbox"]:checked');
const selectedIssues = Array.from(checkedBoxes).map(cb => {
const messageLine = cb.closest('.message-line');
return { id: messageLine.getAttribute('data-id'), message: messageLine.querySelector('span').textContent };
});
if (selectedIssues.length === 0) {
alert('Please select at least one issue to tune.');
return;
}
const detailsRow = button.closest('tr');
let dataRow = detailsRow.previousElementSibling;
while (dataRow && dataRow.classList.contains('expand-details-row')) {
dataRow = dataRow.previousElementSibling;
}
if (!dataRow || !dataRow.classList.contains('expandable-row')) {
console.error('Could not find data row');
return;
}
const rowData = prepareRowForApi(dataRow);
if (!rowData) {
alert('Missing required location data for API call');
return;
}
const apiResponse = await fetchPlaceMatches([rowData]);
if (!apiResponse || !apiResponse.results || apiResponse.results.length === 0) {
alert('No results returned from API');
return;
}
const result = apiResponse.results[0];
if (result.matches && result.matches.length > 0) {
showLocationPopup(result.matches, selectedIssues);
} else {
alert('No location matches found for this entry');
}
}
```
--------------------------------
### Parse Geofeed CSV with Encoding Detection in Python
Source: https://context7.com/fastah/ip-geofeed-skills/llms.txt
Parses RFC 8805 geofeed CSV files, handling encoding detection (UTF-8, UTF-8-SIG, Latin-1), comments starting with '#', and missing columns. It normalizes entries and saves comments to a JSON file.
```python
import csv
import json
from pathlib import Path
def parse_geofeed_csv(filepath):
"""
Parse RFC 8805 geofeed CSV with encoding detection.
Handles comments, missing columns, and UTF-8 BOM.
"""
# Try encodings in order
encodings = ['utf-8', 'utf-8-sig', 'latin-1']
content = None
for encoding in encodings:
try:
with open(filepath, encoding=encoding) as f:
content = f.read()
break
except UnicodeDecodeError:
continue
if content is None:
raise ValueError("Unable to decode input file")
entries = []
comments = {} # line_number -> comment text
for line_num, line in enumerate(content.splitlines(), 1):
stripped = line.strip()
# Handle comments and blank lines
if not stripped or stripped.startswith('#'):
comments[str(line_num)] = line
continue
# Parse CSV row
reader = csv.reader([line])
row = list(reader)[0]
# Pad to 4 columns minimum
while len(row) < 4:
row.append('')
entries.append({
'Line': line_num,
'IPPrefix': row[0].strip(),
'CountryCode': row[1].strip().upper(),
'RegionCode': row[2].strip(),
'City': row[3].strip(),
'PostalCode': row[4].strip() if len(row) > 4 else ''
})
# Save comments for report generation
with open(FASTAH_DATA_DIR / 'comments.json', 'w') as f:
json.dump(comments, f)
return entries, len(row) if entries else 4
```
--------------------------------
### Generate and Download CSV
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Generates a CSV content string, creates a Blob, and simulates a download link for the user. The filename is derived from input metrics and ensured to have a .csv extension.
```javascript
const csvContent = csvLines.join('\n');
// Create download link
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
// Get filename from inputFileMetrics, ensure it has .csv extension
let filename = document.getElementById('inputFileMetrics')
.textContent
.trim()
.split(/[/[]/) // Corrected split regex
.pop() || 'geofeed_report.csv';
if (!filename.toLowerCase().endsWith('.csv')) {
filename += '.csv';
}
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
```
--------------------------------
### Initialize Global State Variables
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Initializes global variables for managing map instances, modal state, and selected items. Ensure these are properly updated during application runtime.
```javascript
let maps = {};
let currentTuningButton = null;
let selectedMatchIndex = 0;
let matchItems = [];
let isModalOpen = false;
let summaryMapInstance = null;
let summaryLayerGroup = null;
let currentMapMode = 'bbox';
let summaryRowData = [];
```
--------------------------------
### Derive Entry-Level Flags
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/SKILL.md
After populating all messages for an entry, use this code to determine the overall status flags for the entry. These flags indicate the presence of errors, warnings, suggestions, or tunable messages.
```python
entry["HasError"] = any(m["Type"] == "ERROR" for m in entry["Messages"])
entry["HasWarning"] = any(m["Type"] == "WARNING" for m in entry["Messages"])
entry["HasSuggestion"] = any(m["Type"] == "SUGGESTION" for m in entry["Messages"])
entry["Tunable"] = any(m["Checked"] for m in entry["Messages"])
```
--------------------------------
### IP Address and Subnet Validation in Python
Source: https://context7.com/fastah/ip-geofeed-skills/llms.txt
Demonstrates parsing and validating IPv4 and IPv6 addresses and subnets using Python's `ipaddress` module. Strict mode is used to catch host bit errors, and a helper function checks for non-public IP ranges.
```python
import ipaddress
# Parse IPv4 and IPv6 addresses
ipaddress.ip_address('192.168.0.1') # IPv4Address
ipaddress.ip_address('2001:db8::') # IPv6Address
# Parse subnets with strict=True to catch errors
ipaddress.ip_network('192.168.0.0/28', strict=True) # Valid
ipaddress.ip_network('192.168.0.1/30', strict=True) # Raises ValueError: host bits set
# Single-host representations
ipaddress.ip_network('192.168.1.1/32', strict=True) # IPv4 single host
ipaddress.ip_network('2001:db8::1/128', strict=True) # IPv6 single host
# Detect non-public IP ranges (private, loopback, multicast, etc.)
def is_non_public(network):
"""Check if a network is non-public for RFC 8805 purposes."""
return (
network.is_private
or network.is_loopback
or network.is_link_local
or network.is_multicast
or network.is_reserved
or not network.is_global
)
# Example usage
net = ipaddress.ip_network('10.0.0.0/8', strict=True)
print(is_non_public(net)) # True - private range not allowed in geofeeds
```
--------------------------------
### Initialize Pagination
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Initializes the pagination controls on page load by calling the updatePagination function to set the initial state.
```javascript
function initializePagination() {
updatePagination();
}
```
--------------------------------
### Handle Row Click and Keyboard Events for Expansion
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Attaches click and keyboard event listeners to rows to toggle the display of detail rows, including issues and previous values. Ensures that only rows with errors, warnings, or suggestions are focusable and expandable. Updates pagination after toggling.
```javascript
=== 'true'; // Only make rows with issues focusable if (hasError || hasWarning || hasSuggestion) { row.setAttribute('tabindex', '0'); // Click handler row.addEventListener('click', function(e) { // Don't expand if clicking on checkbox if (e.target.classList.contains('row-checkbox')) { return; } let detailRow = this.nextElementSibling; let expanding = false; // First pass: check if we are expanding (issues-row will be shown) while (detailRow && detailRow.classList.contains('expand-details-row')) { if (detailRow.classList.contains('issues-row')) { expanding = !detailRow.classList.contains('show'); break; } detailRow = detailRow.nextElementSibling; } detailRow = this.nextElementSibling; while (detailRow && detailRow.classList.contains('expand-details-row')) { if (detailRow.classList.contains('issues-row')) { // Always toggle issues-row detailRow.classList.toggle('show'); } else if (detailRow.classList.contains('previous-values-row')) { if (expanding) { // Show previous-values-row if this row has original data (was tuned) if (row.hasAttribute('data-original-country') || row.hasAttribute('data-original-region') || row.hasAttribute('data-original-city')) { detailRow.classList.add('show'); } } else { // If collapsing, hide previous-values-row detailRow.classList.remove('show'); } } detailRow = detailRow.nextElementSibling; } // Refresh pagination to show/hide the toggled detail rows updatePagination(); }); // Keyboard handler row.addEventListener('keydown', function(e) { // Don't handle if focus is on checkbox if (document.activeElement.classList.contains('row-checkbox')) { return; } if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); let detailRow = this.nextElementSibling; let expanding = false; // First pass: check if we are expanding (issues-row will be shown) while (detailRow && detailRow.classList.contains('expand-details-row')) { if (detailRow.classList.contains('issues-row')) { expanding = !detailRow.classList.contains('show'); break; } detailRow = detailRow.nextElementSibling; } detailRow = this.nextElementSibling; while (detailRow && detailRow.classList.contains('expand-details-row')) { if (detailRow.classList.contains('issues-row')) { // Always toggle issues-row detailRow.classList.toggle('show'); } else if (detailRow.classList.contains('previous-values-row')) { if (expanding) { // Show previous-values-row if this row has original data (was tuned) if (row.hasAttribute('data-original-country') || row.hasAttribute('data-original-region') || row.hasAttribute('data-original-city')) { detailRow.classList.add('show'); } } else { // If collapsing, hide previous-values-row detailRow.classList.remove('show'); } } detailRow = detailRow.nextElementSibling; } // Refresh pagination to show/hide the toggled detail rows updatePagination(); } }); } else { row.style.cursor = 'default'; } });
```
--------------------------------
### Show Location Matches Popup
Source: https://github.com/fastah/ip-geofeed-skills/blob/main/skills/geofeed-tuner/scripts/templates/index.html
Displays a modal popup with location matches, including maps and details. Allows users to select a match and handles the initial state of the popup.
```javascript
function showLocationPopup(matches, selectedIssues = []) {
if (!matches || matches.length === 0) {
return;
}
const matchesList = document.getElementById('matchesList');
matchesList.innerHTML = '';
maps = {};
matchItems = [];
selectedMatchIndex = 0;
matches.forEach((match, index) => {
const matchItem = document.createElement('div');
matchItem.className = 'match-item';
if (index === 0) {
matchItem.classList.add('selected');
}
const mapId = `map-${index}`;
let primaryLocation = match.placeName;
let locationType = 'city';
if (!primaryLocation || primaryLocation.trim() === '') {
primaryLocation = match.stateCode;
locationType = 'region';
}
if (!primaryLocation || primaryLocation.trim() === '') {
primaryLocation = match.countryCode;
locationType = 'country';
}
let secondaryInfo = [];
if (match.placeName && locationType !== 'city') {
secondaryInfo.push(`City: ${escapeHtml(match.placeName)}`);
}
if (match.stateCode && locationType !== 'region') {
secondaryInfo.push(`Region: ${escapeHtml(match.stateCode)}`);
}
if (match.countryCode && locationType !== 'country') {
secondaryInfo.push(`Country: ${escapeHtml(match.countryCode)}`);
}
matchItem.innerHTML = `