enhancement

This commit is contained in:
Francesco Picone
2025-12-29 12:55:53 +01:00
parent 74bdffc411
commit 7c00b1af87
7 changed files with 260 additions and 24 deletions

View File

@@ -1,25 +1,61 @@
from flask import Flask, render_template_string
from flask import Flask, render_template_string, jsonify
from flask_cors import CORS
from cloudflare import get_dns_records
import os
import logging
# Configurazione logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
CORS(app) # Abilita CORS per richieste AJAX
# ----------------------------
# Template HTML già esistente
# Template HTML
# ----------------------------
TEMPLATE = open("templates/index.html").read() # Se preferisci usare file separato
with open("templates/index.html", encoding="utf-8") as f:
TEMPLATE = f.read()
REFRESH_SECONDS = int(os.getenv("REFRESH_SECONDS", "60"))
# ----------------------------
# Endpoint principale
# ----------------------------
@app.route("/")
def index():
records = get_dns_records()
# Passiamo i dati al template
return render_template_string(TEMPLATE, records=records)
try:
records = get_dns_records()
return render_template_string(TEMPLATE, records=records, refresh_seconds=REFRESH_SECONDS)
except Exception as e:
logger.error(f"Errore nel caricamento dei record: {e}")
return render_template_string(TEMPLATE, records=[], refresh_seconds=REFRESH_SECONDS)
# ----------------------------
# API endpoint per refresh AJAX
# ----------------------------
@app.route("/api/records")
def api_records():
try:
records = get_dns_records()
return jsonify({"success": True, "records": records})
except Exception as e:
logger.error(f"Errore API: {e}")
return jsonify({"success": False, "error": str(e)}), 500
# ----------------------------
# Health check
# ----------------------------
@app.route("/health")
def health():
return jsonify({"status": "ok"}), 200
# ----------------------------
# Avvio Flask
# ----------------------------
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
logger.info(f"Avvio server su porta 5000 con refresh ogni {REFRESH_SECONDS}s")
app.run(host="0.0.0.0", port=5000, debug=False)

View File

@@ -5,10 +5,14 @@ from datetime import datetime
# ----------------------------
# Config da .env
# ----------------------------
CF_API_TOKEN = os.getenv("CF_UI_API_TOKEN")
CF_API_TOKEN = os.getenv("CF_API_TOKEN")
CF_ZONE = os.getenv("CF_ZONE")
CF_SUBDOMAINS = os.getenv("CF_UI_SUBDOMAINS", "").split(",")
PUBLIC_IP_API = os.getenv("PUBLIC_IP_API", "https://api.ipify.org") # opzionale
CF_SUBDOMAINS = [s.strip() for s in os.getenv("CF_SUBDOMAINS", "").split(",") if s.strip()]
PUBLIC_IP_API = os.getenv("PUBLIC_IP_API", "https://api.ipify.org")
# Validazione configurazione
if not CF_API_TOKEN or not CF_ZONE or not CF_SUBDOMAINS:
raise ValueError("Mancano variabili ambiente: CF_API_TOKEN, CF_ZONE, CF_SUBDOMAINS")
HEADERS = {
"Authorization": f"Bearer {CF_API_TOKEN}",
@@ -26,16 +30,24 @@ def get_public_ip():
except Exception:
return "N/A"
_zone_id_cache = None
def get_zone_id():
"""Recupera l'ID della zona Cloudflare"""
"""Recupera l'ID della zona Cloudflare (con cache)"""
global _zone_id_cache
if _zone_id_cache:
return _zone_id_cache
try:
resp = requests.get(
"https://api.cloudflare.com/client/v4/zones",
headers=HEADERS,
params={"name": CF_ZONE}
params={"name": CF_ZONE},
timeout=10
).json()
if resp["success"] and resp["result"]:
return resp["result"][0]["id"]
_zone_id_cache = resp["result"][0]["id"]
return _zone_id_cache
else:
raise Exception(f"Zona '{CF_ZONE}' non trovata o errore API.")
except Exception as e:
@@ -57,40 +69,47 @@ def get_dns_records():
resp = requests.get(
f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records",
headers=HEADERS,
params={"name": fqdn}
params={"name": fqdn},
timeout=10
).json()
if not resp["success"] or not resp["result"]:
# Record non trovato
now = datetime.utcnow().isoformat()
records.append({
"name": fqdn,
"dns_ip": "N/A",
"public_ip": public_ip,
"proxied": False,
"status": "MISSING",
"last_updated": datetime.utcnow().isoformat()
"last_updated": now,
"last_updated_human": now
})
continue
dns = resp["result"][0]
last_updated = dns.get("modified_on", datetime.utcnow().isoformat())
records.append({
"name": fqdn,
"dns_ip": dns["content"],
"public_ip": public_ip,
"proxied": dns["proxied"],
"status": "OK" if dns["content"] == public_ip else "MISMATCH",
"last_updated": dns.get("modified_on", datetime.utcnow().isoformat())
"last_updated": last_updated,
"last_updated_human": last_updated
})
except Exception as e:
print(f"[ERROR] get_dns_records ({fqdn}): {e}")
now = datetime.utcnow().isoformat()
records.append({
"name": fqdn,
"dns_ip": "ERROR",
"public_ip": public_ip,
"proxied": False,
"status": "ERROR",
"last_updated": datetime.utcnow().isoformat()
"last_updated": now,
"last_updated_human": now
})
return records

3
ui/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
flask==3.1.0
flask-cors==5.0.0
requests==2.32.3

View File

@@ -112,8 +112,8 @@
</table>
</main>
<footer>
Visualizzazione aggiornata ogni 60 secondi
<footer id="status">
Visualizzazione aggiornata ogni {{ refresh_seconds }} secondi | Ultimo aggiornamento: <span id="last-update">ora</span>
</footer>
<script>
@@ -128,9 +128,41 @@ function timeAgo(date) {
return "pochi secondi fa";
}
document.querySelectorAll('.time').forEach(td => {
td.textContent = timeAgo(td.dataset.timestamp);
});
function updateTimeAgo() {
document.querySelectorAll('.time').forEach(td => {
td.textContent = timeAgo(td.dataset.timestamp);
});
}
function updateRecords() {
fetch('/api/records')
.then(res => res.json())
.then(data => {
if (data.success) {
const tbody = document.querySelector('tbody');
tbody.innerHTML = data.records.map(r => `
<tr>
<td>${r.name}</td>
<td>${r.dns_ip}</td>
<td>${r.public_ip}</td>
<td>${r.proxied ? 'ON' : 'OFF'}</td>
<td><span class="badge ${r.status === 'OK' ? 'ok' : 'bad'}">${r.status}</span></td>
<td class="time" data-timestamp="${r.last_updated}">${timeAgo(r.last_updated)}</td>
</tr>
`).join('');
document.getElementById('last-update').textContent = new Date().toLocaleTimeString('it-IT');
}
})
.catch(err => {
console.error('Errore aggiornamento:', err);
document.getElementById('status').style.color = '#ff5252';
});
}
// Init
updateTimeAgo();
setInterval(updateTimeAgo, 5000);
setInterval(updateRecords, {{ refresh_seconds }} * 1000);
</script>
</body>