From d78b4d5719d2f5815b16ba64819910a478e09eda Mon Sep 17 00:00:00 2001 From: akalan Date: Wed, 23 Sep 2026 16:48:48 +0300 Subject: [PATCH] Traefik ve DNS yonetimi sekmeleri ekle --- app.py | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/app.py b/app.py index 3b0abf2..729e1ab 100644 --- a/app.py +++ b/app.py @@ -16,6 +16,7 @@ CF_ZONE_ID = os.environ["CF_ZONE_ID"] SELF_NAME = os.environ.get("SELF_CONTAINER_NAME", "") API_TOKEN = os.environ["PANEL_API_TOKEN"] SERVER_IP = os.environ.get("SERVER_IP", "148.135.181.126") +TRAEFIK_API_URL = os.environ.get("TRAEFIK_API_URL", "http://traefik:8080") ROUTER_RULE_RE = re.compile(r"^traefik\.http\.routers\.[^.]+\.rule$") @@ -254,6 +255,81 @@ def delete_dns_record(hostname): ) +def list_dns_records(): + records = [] + page = 1 + while True: + r = requests.get( + f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records", + params={"page": page, "per_page": 100}, + headers={"Authorization": f"Bearer {CF_API_TOKEN}"}, + timeout=10, + ).json() + records.extend(r.get("result", [])) + info = r.get("result_info", {}) + if page >= info.get("total_pages", 1): + break + page += 1 + records.sort(key=lambda x: (x.get("type", ""), x.get("name", ""))) + return records + + +def create_dns_record(rtype, name, content, proxied, ttl=1): + return requests.post( + f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records", + headers={"Authorization": f"Bearer {CF_API_TOKEN}", "Content-Type": "application/json"}, + json={"type": rtype, "name": name, "content": content, "ttl": int(ttl), "proxied": proxied}, + timeout=10, + ).json() + + +def update_dns_record(record_id, content, proxied): + return requests.patch( + f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records/{record_id}", + headers={"Authorization": f"Bearer {CF_API_TOKEN}", "Content-Type": "application/json"}, + json={"content": content, "proxied": proxied}, + timeout=10, + ).json() + + +def delete_dns_record_by_id(record_id): + requests.delete( + f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records/{record_id}", + headers={"Authorization": f"Bearer {CF_API_TOKEN}"}, + timeout=10, + ) + + +def list_traefik_routers(): + try: + routers = requests.get(f"{TRAEFIK_API_URL}/api/http/routers", timeout=3).json() + services = requests.get(f"{TRAEFIK_API_URL}/api/http/services", timeout=3).json() + except requests.RequestException: + return None + + services_by_name = {s["name"]: s for s in services} + + result = [] + for r in routers: + if r.get("provider") != "docker": + continue + rule = r.get("rule", "") + hostname = rule.split("`")[1] if "`" in rule else rule + service = services_by_name.get(r.get("service"), {}) + servers = service.get("loadBalancer", {}).get("servers", []) + backend = servers[0].get("url") if servers else None + result.append({ + "name": r.get("name", "").split("@")[0], + "hostname": hostname, + "entrypoints": ", ".join(r.get("entryPoints", [])), + "tls": bool(r.get("tls")), + "status": r.get("status"), + "backend": backend, + }) + result.sort(key=lambda x: x["name"]) + return result + + BASE_STYLE = """ body { font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; padding: 2.5rem; } h1 { color: #38bdf8; margin-bottom: 0.2rem; } @@ -303,6 +379,8 @@ BASE_STYLE = """ NAV = """ """ @@ -474,6 +552,110 @@ SYSTEM_TEMPLATE = """ """ +TRAEFIK_TEMPLATE = """ + + + +Traefik - Deploy Panel + + + + +

Deploy Panel

+""" + NAV + """ +
Traefik'in kendi API'sinden canlı okunan router/servis listesi
+ {% if routers is none %} +

Traefik API'sine ulaşılamadı ({{ api_url }}).

+ {% elif routers %} + + + {% for r in routers %} + + + + + + + + + {% endfor %} +
RouterAdresEntrypointTLSDurumBackend
{{ r.name }}{% if r.hostname %}{{ r.hostname }}{% else %}{{ r.hostname }}{% endif %}{{ r.entrypoints }}{{ '✅' if r.tls else '—' }}{{ r.status }}{{ r.backend or '—' }}
+ {% else %} +

Aktif router yok.

+ {% endif %} + + +""" + +DNS_TEMPLATE = """ + + + +DNS - Deploy Panel + + + + + +

Deploy Panel

+""" + NAV + """ +
akalan.dev · Cloudflare DNS kayıtları · {{ records|length }} kayıt
+ +
+ + + + + +
+ + {% if records %} + + + {% for r in records %} + + + + + + + + {% endfor %} +
TipAdDeğerTuruncu Bulut
{{ r.type }}{{ r.name }} +
+ + + +
+
{{ '🟠 açık' if r.proxied else '⚪ kapalı' }} +
+ +
+
+ {% else %} +

Hiç DNS kaydı yok.

+ {% endif %} + + +""" + LOGS_TEMPLATE = """ @@ -511,6 +693,48 @@ def system_ui(): ) +@app.route("/traefik") +def traefik_ui(): + return render_template_string( + TRAEFIK_TEMPLATE, routers=list_traefik_routers(), api_url=TRAEFIK_API_URL, page="traefik" + ) + + +@app.route("/dns") +def dns_ui(): + return render_template_string(DNS_TEMPLATE, records=list_dns_records(), page="dns") + + +@app.route("/dns/add", methods=["POST"]) +def dns_add(): + rtype = request.form.get("type", "A") + name = request.form.get("name", "").strip() + content = request.form.get("content", "").strip() + proxied = request.form.get("proxied") == "on" + if name and content: + if name == "@": + name = "akalan.dev" + elif not name.endswith("akalan.dev"): + name = f"{name}.akalan.dev" + create_dns_record(rtype, name, content, proxied) + return redirect("/dns") + + +@app.route("/dns/update/", methods=["POST"]) +def dns_update(record_id): + content = request.form.get("content", "").strip() + proxied = request.form.get("proxied") == "on" + if content: + update_dns_record(record_id, content, proxied) + return redirect("/dns") + + +@app.route("/dns/delete/", methods=["POST"]) +def dns_delete(record_id): + delete_dns_record_by_id(record_id) + return redirect("/dns") + + @app.route("/logs/") def logs_ui(name): result = subprocess.run(