Add Web UI
This commit is contained in:
8
.env
8
.env
@@ -15,4 +15,10 @@ GOTIFY_PRIORITY=5
|
|||||||
|
|
||||||
# Configurazione Container
|
# Configurazione Container
|
||||||
DATA_VOLUME=./data
|
DATA_VOLUME=./data
|
||||||
RESTART_POLICY=unless-stopped
|
RESTART_POLICY=unless-stopped
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Cloudflare UI (READ ONLY)
|
||||||
|
# ======================
|
||||||
|
UI_REFRESH_SECONDS=60
|
||||||
|
UI_PORT=8088
|
||||||
@@ -21,3 +21,23 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${DATA_VOLUME}:/config
|
- ${DATA_VOLUME}:/config
|
||||||
|
|
||||||
|
cloudflare-ddns-ui:
|
||||||
|
image: python:3.12-slim
|
||||||
|
container_name: cloudflare-ddns-ui
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- CF_API_TOKEN=${CF_API_TOKEN}
|
||||||
|
- CF_ZONE=${CF_ZONE}
|
||||||
|
- CF_SUBDOMAINS=${CF_SUBDOMAIN}
|
||||||
|
- REFRESH_SECONDS=${UI_REFRESH_SECONDS}
|
||||||
|
volumes:
|
||||||
|
- ./ui:/app
|
||||||
|
working_dir: /app
|
||||||
|
command: >
|
||||||
|
sh -c "pip install --no-cache-dir flask requests &&
|
||||||
|
python app.py"
|
||||||
|
ports:
|
||||||
|
- "${UI_PORT}:8080"
|
||||||
|
|||||||
11
ui/app.py
Normal file
11
ui/app.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from flask import Flask, render_template
|
||||||
|
from cloudflare import get_dns_status
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return render_template("index.html", records=get_dns_status())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=8080)
|
||||||
42
ui/cloudflare.py
Normal file
42
ui/cloudflare.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
CF_API_TOKEN = os.getenv("CF_API_TOKEN")
|
||||||
|
ZONE = os.getenv("CF_ZONE")
|
||||||
|
SUBDOMAINS = os.getenv("CF_SUBDOMAINS").split(",")
|
||||||
|
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"Bearer {CF_API_TOKEN}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_public_ip():
|
||||||
|
return requests.get("https://api.ipify.org", timeout=5).text.strip()
|
||||||
|
|
||||||
|
def get_dns_status():
|
||||||
|
ip_pubblico = get_public_ip()
|
||||||
|
out = []
|
||||||
|
|
||||||
|
zone = requests.get(
|
||||||
|
"https://api.cloudflare.com/client/v4/zones",
|
||||||
|
headers=HEADERS,
|
||||||
|
params={"name": ZONE}
|
||||||
|
).json()["result"][0]
|
||||||
|
|
||||||
|
for sub in SUBDOMAINS:
|
||||||
|
fqdn = f"{sub}.{ZONE}"
|
||||||
|
dns = requests.get(
|
||||||
|
f"https://api.cloudflare.com/client/v4/zones/{zone['id']}/dns_records",
|
||||||
|
headers=HEADERS,
|
||||||
|
params={"name": fqdn}
|
||||||
|
).json()["result"][0]
|
||||||
|
|
||||||
|
out.append({
|
||||||
|
"name": fqdn,
|
||||||
|
"dns_ip": dns["content"],
|
||||||
|
"public_ip": ip_pubblico,
|
||||||
|
"proxied": dns["proxied"],
|
||||||
|
"status": "OK" if dns["content"] == ip_pubblico else "MISMATCH"
|
||||||
|
})
|
||||||
|
|
||||||
|
return out
|
||||||
42
ui/templates/index.html
Normal file
42
ui/templates/index.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Cloudflare DDNS Status</title>
|
||||||
|
<meta http-equiv="refresh" content="{{ 60 }}">
|
||||||
|
<style>
|
||||||
|
body { background:#0f1115; color:#eee; font-family:sans-serif; padding:20px }
|
||||||
|
table { width:100%; border-collapse:collapse }
|
||||||
|
th, td { padding:10px; border-bottom:1px solid #333 }
|
||||||
|
.ok { color:#4caf50 }
|
||||||
|
.bad { color:#ff5252 }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h2>Cloudflare DDNS – Stato DNS</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Record</th>
|
||||||
|
<th>DNS IP</th>
|
||||||
|
<th>IP Pubblico</th>
|
||||||
|
<th>Proxy</th>
|
||||||
|
<th>Stato</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% for r in records %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ r.name }}</td>
|
||||||
|
<td>{{ r.dns_ip }}</td>
|
||||||
|
<td>{{ r.public_ip }}</td>
|
||||||
|
<td>{{ "ON" if r.proxied else "OFF" }}</td>
|
||||||
|
<td class="{{ 'ok' if r.status == 'OK' else 'bad' }}">
|
||||||
|
{{ r.status }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user