httpd: consolidate map page with shared css
This commit is contained in:
@@ -219,6 +219,11 @@ body {
|
|||||||
font-size: 13px; background: var(--bg); color: var(--text);
|
font-size: 13px; background: var(--bg); color: var(--text);
|
||||||
padding: 16px; min-height: 100vh; line-height: 1.4;
|
padding: 16px; min-height: 100vh; line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
a { color: var(--blue); text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
h1 { font-size: 18px; font-weight: 600; color: var(--text); margin-bottom: 16px; }
|
||||||
|
h2 { font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 12px; }
|
||||||
|
h3 { font-size: 13px; font-weight: 600; color: var(--dim); margin-bottom: 8px; }
|
||||||
.container { max-width: 1400px; margin: 0 auto; }
|
.container { max-width: 1400px; margin: 0 auto; }
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
@@ -360,6 +365,28 @@ body {
|
|||||||
.pct-label { font-size: 10px; color: var(--dim); text-transform: uppercase; }
|
.pct-label { font-size: 10px; color: var(--dim); text-transform: uppercase; }
|
||||||
.pct-value { font-size: 16px; font-weight: 700; margin-top: 2px; }
|
.pct-value { font-size: 16px; font-weight: 700; margin-top: 2px; }
|
||||||
|
|
||||||
|
/* Map page */
|
||||||
|
.nav { margin-bottom: 16px; font-size: 12px; }
|
||||||
|
.map-stats { margin-bottom: 16px; color: var(--dim); font-size: 12px; padding: 8px 12px; background: var(--card); border: 1px solid var(--border); border-radius: 6px; }
|
||||||
|
.country-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 8px; }
|
||||||
|
.country { padding: 12px 8px; border-radius: 6px; text-align: center; background: var(--card); border: 1px solid var(--border); transition: transform 0.15s, box-shadow 0.15s; }
|
||||||
|
.country:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
|
||||||
|
.country .code { font-weight: bold; font-size: 1.1em; letter-spacing: 0.5px; }
|
||||||
|
.country .count { font-size: 0.85em; color: var(--dim); margin-top: 4px; font-feature-settings: "tnum"; }
|
||||||
|
.country.t1 { background: #0d4429; border-color: #238636; }
|
||||||
|
.country.t1 .code { color: #7ee787; }
|
||||||
|
.country.t2 { background: #1a3d2e; border-color: #2ea043; }
|
||||||
|
.country.t2 .code { color: #7ee787; }
|
||||||
|
.country.t3 { background: #1f3d2a; border-color: #3fb950; }
|
||||||
|
.country.t3 .code { color: #56d364; }
|
||||||
|
.country.t4 { background: #2a4a35; border-color: #56d364; }
|
||||||
|
.country.t4 .code { color: #3fb950; }
|
||||||
|
.country.t5 { background: #35573f; border-color: #7ee787; }
|
||||||
|
.country.t5 .code { color: #3fb950; }
|
||||||
|
.map-legend { display: flex; gap: 16px; margin-top: 20px; flex-wrap: wrap; padding: 12px; background: var(--card); border: 1px solid var(--border); border-radius: 6px; }
|
||||||
|
.map-legend .legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--dim); }
|
||||||
|
.map-legend .legend-dot { width: 12px; height: 12px; border-radius: 3px; }
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
.ftr { text-align: center; font-size: 11px; color: var(--dim); padding: 16px 0; margin-top: 8px; border-top: 1px solid var(--border); }
|
.ftr { text-align: center; font-size: 11px; color: var(--dim); padding: 16px 0; margin-top: 8px; border-top: 1px solid var(--border); }
|
||||||
'''
|
'''
|
||||||
@@ -705,6 +732,54 @@ fetchStats();
|
|||||||
setInterval(fetchStats, 3000);
|
setInterval(fetchStats, 3000);
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
MAP_HTML = '''<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>PPF Proxy Map</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="nav"><a href="/dashboard">← Dashboard</a></div>
|
||||||
|
<h1>Proxy Distribution by Country</h1>
|
||||||
|
<div class="map-stats" id="stats">Loading...</div>
|
||||||
|
<div class="country-grid" id="grid"></div>
|
||||||
|
<div class="map-legend">
|
||||||
|
<div class="legend-item"><div class="legend-dot" style="background:#0d4429"></div> 1000+</div>
|
||||||
|
<div class="legend-item"><div class="legend-dot" style="background:#1a3d2e"></div> 500-999</div>
|
||||||
|
<div class="legend-item"><div class="legend-dot" style="background:#1f3d2a"></div> 100-499</div>
|
||||||
|
<div class="legend-item"><div class="legend-dot" style="background:#2a4a35"></div> 10-99</div>
|
||||||
|
<div class="legend-item"><div class="legend-dot" style="background:#35573f"></div> 1-9</div>
|
||||||
|
</div>
|
||||||
|
<div class="ftr">PPF Python Proxy Finder</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
fetch('/api/countries')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
var countries = data.countries || {};
|
||||||
|
var entries = Object.entries(countries).sort((a,b) => b[1] - a[1]);
|
||||||
|
var total = entries.reduce((s, e) => s + e[1], 0);
|
||||||
|
document.getElementById('stats').textContent = entries.length + ' countries, ' + total.toLocaleString() + ' working proxies';
|
||||||
|
var html = '';
|
||||||
|
entries.forEach(function(e) {
|
||||||
|
var code = e[0], count = e[1];
|
||||||
|
var tier = count >= 1000 ? 't1' : count >= 500 ? 't2' : count >= 100 ? 't3' : count >= 10 ? 't4' : 't5';
|
||||||
|
html += '<div class="country ' + tier + '"><div class="code">' + code + '</div><div class="count">' + count.toLocaleString() + '</div></div>';
|
||||||
|
});
|
||||||
|
document.getElementById('grid').innerHTML = html;
|
||||||
|
})
|
||||||
|
.catch(function(e) {
|
||||||
|
document.getElementById('stats').textContent = 'Error loading data';
|
||||||
|
document.getElementById('stats').style.color = 'var(--red)';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
|
||||||
DASHBOARD_HTML = '''<!DOCTYPE html>
|
DASHBOARD_HTML = '''<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@@ -716,7 +791,7 @@ DASHBOARD_HTML = '''<!DOCTYPE html>
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hdr">
|
<div class="hdr">
|
||||||
<h1>PPF Dashboard</h1>
|
<h1>PPF Dashboard <a href="/map" style="font-size:12px;font-weight:normal;color:var(--dim);margin-left:12px">Map →</a></h1>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<span class="mode-badge mode-ssl" id="checktypeBadge">-</span>
|
<span class="mode-badge mode-ssl" id="checktypeBadge">-</span>
|
||||||
<span class="mode-badge mode-profile" id="profileBadge" style="display:none">PROFILING</span>
|
<span class="mode-badge mode-profile" id="profileBadge" style="display:none">PROFILING</span>
|
||||||
@@ -960,7 +1035,7 @@ DASHBOARD_HTML = '''<!DOCTYPE html>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ftr">PPF Proxy Fetcher</div>
|
<div class="ftr">PPF Python Proxy Finder</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/dashboard.js"></script>
|
<script src="/static/dashboard.js"></script>
|
||||||
</body>
|
</body>
|
||||||
@@ -1006,9 +1081,11 @@ class ProxyAPIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
routes = {
|
routes = {
|
||||||
'/': self.handle_index,
|
'/': self.handle_index,
|
||||||
'/dashboard': self.handle_dashboard,
|
'/dashboard': self.handle_dashboard,
|
||||||
|
'/map': self.handle_map,
|
||||||
'/static/style.css': self.handle_css,
|
'/static/style.css': self.handle_css,
|
||||||
'/static/dashboard.js': self.handle_js,
|
'/static/dashboard.js': self.handle_js,
|
||||||
'/api/stats': self.handle_stats,
|
'/api/stats': self.handle_stats,
|
||||||
|
'/api/countries': self.handle_countries,
|
||||||
'/proxies': self.handle_proxies,
|
'/proxies': self.handle_proxies,
|
||||||
'/proxies/count': self.handle_count,
|
'/proxies/count': self.handle_count,
|
||||||
'/health': self.handle_health,
|
'/health': self.handle_health,
|
||||||
@@ -1033,6 +1110,22 @@ class ProxyAPIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
def handle_dashboard(self):
|
def handle_dashboard(self):
|
||||||
self.send_html(DASHBOARD_HTML)
|
self.send_html(DASHBOARD_HTML)
|
||||||
|
|
||||||
|
def handle_map(self):
|
||||||
|
self.send_html(MAP_HTML)
|
||||||
|
|
||||||
|
def handle_countries(self):
|
||||||
|
"""Return all countries with proxy counts."""
|
||||||
|
try:
|
||||||
|
db = mysqlite.mysqlite(self.database, str)
|
||||||
|
rows = db.execute(
|
||||||
|
'SELECT country, COUNT(*) as c FROM proxylist WHERE failed=0 AND country IS NOT NULL '
|
||||||
|
'GROUP BY country ORDER BY c DESC'
|
||||||
|
).fetchall()
|
||||||
|
countries = {r[0]: r[1] for r in rows}
|
||||||
|
self.send_json({'countries': countries})
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'error': str(e)}, 500)
|
||||||
|
|
||||||
def handle_css(self):
|
def handle_css(self):
|
||||||
self.send_css(DASHBOARD_CSS)
|
self.send_css(DASHBOARD_CSS)
|
||||||
|
|
||||||
@@ -1208,7 +1301,9 @@ class ProxyAPIServer(threading.Thread):
|
|||||||
body = json.dumps({
|
body = json.dumps({
|
||||||
'endpoints': {
|
'endpoints': {
|
||||||
'/dashboard': 'web dashboard (HTML)',
|
'/dashboard': 'web dashboard (HTML)',
|
||||||
|
'/map': 'proxy distribution by country (HTML)',
|
||||||
'/api/stats': 'runtime statistics (JSON)',
|
'/api/stats': 'runtime statistics (JSON)',
|
||||||
|
'/api/countries': 'proxy counts by country (JSON)',
|
||||||
'/proxies': 'list working proxies (params: limit, proto, country, asn)',
|
'/proxies': 'list working proxies (params: limit, proto, country, asn)',
|
||||||
'/proxies/count': 'count working proxies',
|
'/proxies/count': 'count working proxies',
|
||||||
'/health': 'health check',
|
'/health': 'health check',
|
||||||
@@ -1217,6 +1312,8 @@ class ProxyAPIServer(threading.Thread):
|
|||||||
return body, 'application/json', 200
|
return body, 'application/json', 200
|
||||||
elif path == '/dashboard':
|
elif path == '/dashboard':
|
||||||
return DASHBOARD_HTML, 'text/html; charset=utf-8', 200
|
return DASHBOARD_HTML, 'text/html; charset=utf-8', 200
|
||||||
|
elif path == '/map':
|
||||||
|
return MAP_HTML, 'text/html; charset=utf-8', 200
|
||||||
elif path == '/static/style.css':
|
elif path == '/static/style.css':
|
||||||
# Use str.format() instead of % to avoid issues with % escaping
|
# Use str.format() instead of % to avoid issues with % escaping
|
||||||
css = DASHBOARD_CSS
|
css = DASHBOARD_CSS
|
||||||
@@ -1239,6 +1336,17 @@ class ProxyAPIServer(threading.Thread):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return json.dumps(stats, indent=2), 'application/json', 200
|
return json.dumps(stats, indent=2), 'application/json', 200
|
||||||
|
elif path == '/api/countries':
|
||||||
|
try:
|
||||||
|
db = mysqlite.mysqlite(self.database, str)
|
||||||
|
rows = db.execute(
|
||||||
|
'SELECT country, COUNT(*) as c FROM proxylist WHERE failed=0 AND country IS NOT NULL '
|
||||||
|
'GROUP BY country ORDER BY c DESC'
|
||||||
|
).fetchall()
|
||||||
|
countries = {r[0]: r[1] for r in rows}
|
||||||
|
return json.dumps({'countries': countries}, indent=2), 'application/json', 200
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({'error': str(e)}), 'application/json', 500
|
||||||
elif path == '/proxies':
|
elif path == '/proxies':
|
||||||
try:
|
try:
|
||||||
db = mysqlite.mysqlite(self.database, str)
|
db = mysqlite.mysqlite(self.database, str)
|
||||||
|
|||||||
Reference in New Issue
Block a user