Latest Version
This commit is contained in:
@@ -4,4 +4,4 @@ COPY requirements.txt .
|
|||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
COPY . .
|
COPY . .
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
CMD ["python","app.py"]
|
CMD ["python","app.py"]
|
||||||
20
README.md
20
README.md
@@ -1,19 +1,3 @@
|
|||||||
# IP WebApp
|
# myip WebApp Modern UI
|
||||||
|
|
||||||
Una semplice webapp Flask che mostra:
|
Include Montserrat, animations, copy-IP button, favicon.
|
||||||
- IP pubblico del visitatore
|
|
||||||
- Informazioni RIPE (ASN, holder, prefix)
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```
|
|
||||||
docker build -t ip-webapp .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run --rm -p 8080:8000 ip-webapp
|
|
||||||
```
|
|
||||||
|
|
||||||
Visita: http://localhost:8080
|
|
||||||
|
|||||||
125
app.py
125
app.py
@@ -1,53 +1,138 @@
|
|||||||
from flask import Flask, request, render_template_string
|
from flask import Flask, request, render_template_string
|
||||||
import requests
|
import requests
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)
|
||||||
|
|
||||||
HTML_TEMPLATE = """<!doctype html>
|
HTML_TEMPLATE = """<!doctype html>
|
||||||
<html lang="it">
|
<html lang="it">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Il tuo IP pubblico</title>
|
<title>myip – IP & RIPE Lookup</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/x-icon" href="/static/favicon.png">
|
||||||
|
<link rel="stylesheet" href="/static/myip.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Il tuo IP pubblico è: {{ ip }}</h2>
|
<div class="card">
|
||||||
{% if ripe %}
|
{% if show_logo %}
|
||||||
<h3>Informazioni RIPE</h3>
|
<img src="/static/logo.png" class="logo" alt="Logo">
|
||||||
<p>Provider: {{ ripe.holder }}</p>
|
{% endif %}
|
||||||
<p>Prefisso: {{ ripe.prefix }}</p>
|
<div class="badge">
|
||||||
<p>ASN: {{ ripe.asns|join(', ') }}</p>
|
<span class="badge-dot"></span>
|
||||||
{% endif %}
|
<span>IP & RIPE Lookup</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="title">Il tuo IP pubblico è:</h1>
|
||||||
|
|
||||||
|
<div class="ip-wrapper">
|
||||||
|
<span id="ip-value" class="ip">{{ ip }}</span>
|
||||||
|
<button id="copy-btn" class="copy-btn" type="button">
|
||||||
|
<span class="copy-icon">📋</span>
|
||||||
|
<span class="copy-label">Copia IP</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
{% if ripe %}
|
||||||
|
<div class="ripe-title">Informazioni RIPE</div>
|
||||||
|
{% if ripe.holder %}
|
||||||
|
<div class="ripe-item"><strong>Provider:</strong> {{ ripe.holder }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if ripe.prefix %}
|
||||||
|
<div class="ripe-item"><strong>Prefisso:</strong> {{ ripe.prefix }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if ripe.asns %}
|
||||||
|
<div class="ripe-item"><strong>ASN:</strong> {{ ripe.asns | join(', ') }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="ripe-muted">Dati ottenuti tramite RIPEstat Data API.</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="ripe-title">Informazioni RIPE</div>
|
||||||
|
<div class="ripe-muted">
|
||||||
|
Non è stato possibile recuperare i dettagli RIPE per questo indirizzo.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
var card = document.querySelector(".card");
|
||||||
|
if (card) card.classList.add("card-visible");
|
||||||
|
|
||||||
|
var btn = document.getElementById("copy-btn");
|
||||||
|
var ipSpan = document.getElementById("ip-value");
|
||||||
|
if (!btn || !ipSpan) return;
|
||||||
|
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
var ip = ipSpan.textContent.trim();
|
||||||
|
if (!ip) return;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(ip).then(function () {
|
||||||
|
var original = btn.querySelector(".copy-label").textContent;
|
||||||
|
btn.classList.add("copy-btn-success");
|
||||||
|
btn.querySelector(".copy-label").textContent = "Copiato!";
|
||||||
|
setTimeout(function () {
|
||||||
|
btn.classList.remove("copy-btn-success");
|
||||||
|
btn.querySelector(".copy-label").textContent = original;
|
||||||
|
}, 1500);
|
||||||
|
}).catch(console.error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
RIPESTAT_BASE = "https://stat.ripe.net/data"
|
||||||
|
|
||||||
def get_client_ip():
|
def get_client_ip():
|
||||||
xff = request.headers.get("X-Forwarded-For", "")
|
xff = request.headers.get("X-Forwarded-For", "")
|
||||||
if xff:
|
if xff:
|
||||||
return xff.split(",")[0].strip()
|
parts = [p.strip() for p in xff.split(",") if p.strip()]
|
||||||
|
if parts:
|
||||||
|
return parts[0]
|
||||||
|
xri = request.headers.get("X-Real-IP")
|
||||||
|
if xri:
|
||||||
|
return xri.strip()
|
||||||
return request.remote_addr or "Sconosciuto"
|
return request.remote_addr or "Sconosciuto"
|
||||||
|
|
||||||
def fetch_ripe_info(ip: str):
|
def fetch_ripe_info(ip: str):
|
||||||
try:
|
try:
|
||||||
ni = requests.get("https://stat.ripe.net/data/network-info/data.json",
|
ni_resp = requests.get(f"{RIPESTAT_BASE}/network-info/data.json", params={"resource": ip}, timeout=2)
|
||||||
params={"resource": ip}, timeout=2).json().get("data", {})
|
ni_resp.raise_for_status()
|
||||||
prefix = ni.get("prefix")
|
ni_data = ni_resp.json().get("data", {})
|
||||||
asns = ni.get("asns") or []
|
|
||||||
holder=None
|
prefix = ni_data.get("prefix")
|
||||||
|
asns = ni_data.get("asns") or []
|
||||||
|
holder = None
|
||||||
|
|
||||||
if asns:
|
if asns:
|
||||||
ao = requests.get("https://stat.ripe.net/data/as-overview/data.json",
|
first_asn = asns[0]
|
||||||
params={"resource": asns[0]}, timeout=2).json().get("data", {})
|
ao_resp = requests.get(f"{RIPESTAT_BASE}/as-overview/data.json", params={"resource": first_asn}, timeout=2)
|
||||||
holder = ao.get("holder")
|
ao_resp.raise_for_status()
|
||||||
return {"prefix": prefix, "asns":[f"AS{a}" for a in asns], "holder": holder}
|
holder = ao_resp.json().get("data", {}).get("holder")
|
||||||
|
|
||||||
|
class R: pass
|
||||||
|
r = R()
|
||||||
|
r.prefix = prefix
|
||||||
|
r.asns = [f"AS{x}" for x in asns] if asns else []
|
||||||
|
r.holder = holder
|
||||||
|
|
||||||
|
return r if any([r.prefix, r.asns, r.holder]) else None
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
ip = get_client_ip()
|
ip = get_client_ip()
|
||||||
ripe = fetch_ripe_info(ip) if ip!="Sconosciuto" else None
|
ripe = fetch_ripe_info(ip) if ip != "Sconosciuto" else None
|
||||||
return render_template_string(HTML_TEMPLATE, ip=ip, ripe=ripe)
|
return render_template_string(HTML_TEMPLATE, ip=ip, ripe=ripe, show_logo=True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=8000)
|
app.run(host="0.0.0.0", port=8000)
|
||||||
|
|||||||
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
static/logo.png
BIN
static/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 11 KiB |
37
static/myip.css
Normal file
37
static/myip.css
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: 'Montserrat', Helvetica, Arial, sans-serif;
|
||||||
|
background: #0181C4;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 2.5rem 3rem;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.6);
|
||||||
|
max-width: 520px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #101F2D;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.ripe-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #101F2D;
|
||||||
|
}
|
||||||
|
.ripe-item {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #101F2D;
|
||||||
|
margin: 0.2rem 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user