import json import os import re import subprocess from datetime import datetime, timezone import requests from flask import Flask, jsonify, redirect, render_template_string, request app = Flask(__name__) CF_API_TOKEN = os.environ["CF_API_TOKEN"] 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") ROUTER_RULE_RE = re.compile(r"^traefik\.http\.routers\.[^.]+\.rule$") def check_auth(): auth = request.headers.get("Authorization", "") return auth == f"Bearer {API_TOKEN}" def extract_hostname(labels): """Scan ALL traefik router rule labels, not just one matching the container's own name — compose-based containers get auto-generated docker names that no longer match the Traefik router name.""" for key, value in labels.items(): if ROUTER_RULE_RE.match(key) and "`" in value: return value.split("`")[1] return None def human_relative(iso_ts): if not iso_ts or iso_ts.startswith("0001-01-01"): return None try: dt = datetime.fromisoformat(iso_ts.replace("Z", "+00:00")) except ValueError: return None secs = int((datetime.now(timezone.utc) - dt).total_seconds()) if secs < 60: return f"{secs} sn önce" mins = secs // 60 if mins < 60: return f"{mins} dk önce" hours = mins // 60 if hours < 24: return f"{hours} sa önce" days = hours // 24 return f"{days} gün önce" def inspect_all(cid): return json.loads(subprocess.run( ["docker", "inspect", cid], capture_output=True, text=True, check=True ).stdout)[0] def list_projects(): out = subprocess.run( ["docker", "ps", "-a", "--filter", "label=traefik.enable=true", "--format", "{{.ID}}"], capture_output=True, text=True, check=True, ).stdout.split() projects = [] for cid in out: info = inspect_all(cid) name = info["Name"].lstrip("/") if name == SELF_NAME: continue labels = info["Config"]["Labels"] or {} state = info["State"] status = state["Status"] when = human_relative(state.get("StartedAt") if status == "running" else state.get("FinishedAt")) projects.append({ "name": name, "hostname": extract_hostname(labels), "status": status, "since": when, "image": info["Config"]["Image"], }) projects.sort(key=lambda p: p["name"]) return projects def find_container_hostname(name): result = subprocess.run(["docker", "inspect", name], capture_output=True, text=True) if result.returncode != 0: return None info = json.loads(result.stdout)[0] labels = info["Config"]["Labels"] or {} return extract_hostname(labels) def ensure_dns(hostname): existing = requests.get( f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records", params={"type": "A", "name": hostname}, headers={"Authorization": f"Bearer {CF_API_TOKEN}"}, timeout=10, ).json() if existing.get("result"): return {"created": False} 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": "A", "name": hostname, "content": SERVER_IP, "ttl": 1, "proxied": True}, timeout=10, ) return {"created": True} def delete_dns_record(hostname): if not hostname: return r = requests.get( f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records", params={"name": hostname}, headers={"Authorization": f"Bearer {CF_API_TOKEN}"}, timeout=10, ).json() for rec in r.get("result", []): requests.delete( f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records/{rec['id']}", headers={"Authorization": f"Bearer {CF_API_TOKEN}"}, timeout=10, ) TEMPLATE = """
| Proje | Adres | Durum | İmaj | |
|---|---|---|---|---|
| {{ p.name }} | {% if p.hostname %}{{ p.hostname }}{% else %}—{% endif %} | {{ p.status }} {% if p.since %}{{ p.since }}{% endif %} | {{ p.image }} |
Henüz deploy edilmiş bir proje yok.
{% endif %} """ LOGS_TEMPLATE = """{{ logs }}
"""
@app.route("/")
def index():
return render_template_string(TEMPLATE, projects=list_projects())
@app.route("/logs/