Traefik ve DNS yonetimi sekmeleri ekle
Deploy / deploy (push) Successful in 19s

This commit is contained in:
2026-09-23 16:48:48 +03:00
parent 5d2af35046
commit d78b4d5719
+224
View File
@@ -16,6 +16,7 @@ CF_ZONE_ID = os.environ["CF_ZONE_ID"]
SELF_NAME = os.environ.get("SELF_CONTAINER_NAME", "") SELF_NAME = os.environ.get("SELF_CONTAINER_NAME", "")
API_TOKEN = os.environ["PANEL_API_TOKEN"] API_TOKEN = os.environ["PANEL_API_TOKEN"]
SERVER_IP = os.environ.get("SERVER_IP", "148.135.181.126") 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$") 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 = """ BASE_STYLE = """
body { font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; padding: 2.5rem; } body { font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; padding: 2.5rem; }
h1 { color: #38bdf8; margin-bottom: 0.2rem; } h1 { color: #38bdf8; margin-bottom: 0.2rem; }
@@ -303,6 +379,8 @@ BASE_STYLE = """
NAV = """ NAV = """
<nav> <nav>
<a href="/" class="{{ 'active' if page == 'containers' else '' }}">Container'lar</a> <a href="/" class="{{ 'active' if page == 'containers' else '' }}">Container'lar</a>
<a href="/traefik" class="{{ 'active' if page == 'traefik' else '' }}">Traefik</a>
<a href="/dns" class="{{ 'active' if page == 'dns' else '' }}">DNS</a>
<a href="/system" class="{{ 'active' if page == 'system' else '' }}">Sistem &amp; Disk</a> <a href="/system" class="{{ 'active' if page == 'system' else '' }}">Sistem &amp; Disk</a>
</nav> </nav>
""" """
@@ -474,6 +552,110 @@ SYSTEM_TEMPLATE = """
</html> </html>
""" """
TRAEFIK_TEMPLATE = """
<!doctype html>
<html>
<head>
<title>Traefik - Deploy Panel</title>
<meta charset="utf-8">
<style>""" + BASE_STYLE + """</style>
</head>
<body>
<h1>Deploy Panel</h1>
""" + NAV + """
<div class="sub">Traefik'in kendi API'sinden canlı okunan router/servis listesi</div>
{% if routers is none %}
<p class="empty">Traefik API'sine ulaşılamadı ({{ api_url }}).</p>
{% elif routers %}
<table>
<tr><th>Router</th><th>Adres</th><th>Entrypoint</th><th>TLS</th><th>Durum</th><th>Backend</th></tr>
{% for r in routers %}
<tr>
<td>{{ r.name }}</td>
<td>{% if r.hostname %}<a href="https://{{ r.hostname }}" target="_blank">{{ r.hostname }}</a>{% else %}{{ r.hostname }}{% endif %}</td>
<td>{{ r.entrypoints }}</td>
<td>{{ '' if r.tls else '' }}</td>
<td><span class="badge {{ 'running' if r.status == 'enabled' else 'exited' }}">{{ r.status }}</span></td>
<td><code>{{ r.backend or '' }}</code></td>
</tr>
{% endfor %}
</table>
{% else %}
<p class="empty">Aktif router yok.</p>
{% endif %}
</body>
</html>
"""
DNS_TEMPLATE = """
<!doctype html>
<html>
<head>
<title>DNS - Deploy Panel</title>
<meta charset="utf-8">
<style>""" + BASE_STYLE + """</style>
<style>
.dns-form { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; margin-bottom: 1.5rem;
background: #1e293b; padding: 1rem; border-radius: 10px; }
.dns-form input, .dns-form select {
background: #0f172a; border: 1px solid #334155; color: #e2e8f0; padding: 0.45rem 0.6rem;
border-radius: 6px; font-size: 0.85rem;
}
.dns-form label { font-size: 0.8rem; opacity: 0.7; display: flex; align-items: center; gap: 0.3rem; }
.btn-add { background: #065f46; }
.btn-add:hover { background: #047857; }
.edit-row input[type=text] { width: 100%; }
</style>
</head>
<body>
<h1>Deploy Panel</h1>
""" + NAV + """
<div class="sub">akalan.dev · Cloudflare DNS kayıtları · {{ records|length }} kayıt</div>
<form class="dns-form" method="post" action="/dns/add">
<select name="type">
<option value="A">A</option>
<option value="CNAME">CNAME</option>
<option value="TXT">TXT</option>
<option value="MX">MX</option>
</select>
<input type="text" name="name" placeholder="ad (örn. blog veya blog.akalan.dev)" required>
<input type="text" name="content" placeholder="değer (örn. 148.135.181.126)" required>
<label><input type="checkbox" name="proxied" checked> turuncu bulut</label>
<button class="btn-add" type="submit">Ekle</button>
</form>
{% if records %}
<table>
<tr><th>Tip</th><th>Ad</th><th>Değer</th><th>Turuncu Bulut</th><th></th></tr>
{% for r in records %}
<tr class="edit-row">
<td>{{ r.type }}</td>
<td>{{ r.name }}</td>
<td>
<form method="post" action="/dns/update/{{ r.id }}" style="display:flex; gap:0.4rem; align-items:center;">
<input type="text" name="content" value="{{ r.content }}">
<label><input type="checkbox" name="proxied" {{ 'checked' if r.proxied else '' }}> proxy</label>
<button class="btn-restart" type="submit">Güncelle</button>
</form>
</td>
<td>{{ '🟠 açık' if r.proxied else '⚪ kapalı' }}</td>
<td>
<form method="post" action="/dns/delete/{{ r.id }}"
onsubmit="return confirm('{{ r.name }} ({{ r.type }}) kaydı silinsin mi?');">
<button class="btn-delete" type="submit">Sil</button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p class="empty">Hiç DNS kaydı yok.</p>
{% endif %}
</body>
</html>
"""
LOGS_TEMPLATE = """ LOGS_TEMPLATE = """
<!doctype html> <!doctype html>
<html> <html>
@@ -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/<record_id>", 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/<record_id>", methods=["POST"])
def dns_delete(record_id):
delete_dns_record_by_id(record_id)
return redirect("/dns")
@app.route("/logs/<name>") @app.route("/logs/<name>")
def logs_ui(name): def logs_ui(name):
result = subprocess.run( result = subprocess.run(