This commit is contained in:
@@ -16,7 +16,6 @@ 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$")
|
||||||
|
|
||||||
@@ -27,9 +26,6 @@ def check_auth():
|
|||||||
|
|
||||||
|
|
||||||
def extract_hostname(labels):
|
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():
|
for key, value in labels.items():
|
||||||
if ROUTER_RULE_RE.match(key) and "`" in value:
|
if ROUTER_RULE_RE.match(key) and "`" in value:
|
||||||
return value.split("`")[1]
|
return value.split("`")[1]
|
||||||
@@ -157,46 +153,6 @@ def list_all_containers():
|
|||||||
return containers
|
return containers
|
||||||
|
|
||||||
|
|
||||||
def list_images():
|
|
||||||
out = subprocess.run(
|
|
||||||
["docker", "images", "--format", "{{json .}}"],
|
|
||||||
capture_output=True, text=True, check=True,
|
|
||||||
).stdout.strip().splitlines()
|
|
||||||
images = []
|
|
||||||
for line in out:
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
d = json.loads(line)
|
|
||||||
repo, tag = d.get("Repository", ""), d.get("Tag", "")
|
|
||||||
if repo == "<none>" and tag == "<none>":
|
|
||||||
label = d.get("ID", "")[:19]
|
|
||||||
else:
|
|
||||||
label = f"{repo}:{tag}"
|
|
||||||
images.append({
|
|
||||||
"id": d.get("ID"),
|
|
||||||
"label": label,
|
|
||||||
"size": d.get("Size"),
|
|
||||||
"created": d.get("CreatedSince"),
|
|
||||||
})
|
|
||||||
images.sort(key=lambda i: i["label"])
|
|
||||||
return images
|
|
||||||
|
|
||||||
|
|
||||||
def list_volumes():
|
|
||||||
out = subprocess.run(
|
|
||||||
["docker", "volume", "ls", "--format", "{{json .}}"],
|
|
||||||
capture_output=True, text=True, check=True,
|
|
||||||
).stdout.strip().splitlines()
|
|
||||||
volumes = []
|
|
||||||
for line in out:
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
d = json.loads(line)
|
|
||||||
volumes.append({"name": d.get("Name"), "driver": d.get("Driver")})
|
|
||||||
volumes.sort(key=lambda v: v["name"])
|
|
||||||
return volumes
|
|
||||||
|
|
||||||
|
|
||||||
def docker_system_df():
|
def docker_system_df():
|
||||||
out = subprocess.run(["docker", "system", "df"], capture_output=True, text=True).stdout
|
out = subprocess.run(["docker", "system", "df"], capture_output=True, text=True).stdout
|
||||||
lines = out.strip().splitlines()
|
lines = out.strip().splitlines()
|
||||||
@@ -255,86 +211,18 @@ def delete_dns_record(hostname):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def list_dns_records():
|
TEMPLATE = """
|
||||||
records = []
|
<!doctype html>
|
||||||
page = 1
|
<html>
|
||||||
while True:
|
<head>
|
||||||
r = requests.get(
|
<title>Deploy Panel</title>
|
||||||
f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records",
|
<meta charset="utf-8">
|
||||||
params={"page": page, "per_page": 100},
|
<meta http-equiv="refresh" content="20">
|
||||||
headers={"Authorization": f"Bearer {CF_API_TOKEN}"},
|
<style>
|
||||||
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"].split("@")[0]: 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; }
|
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; }
|
||||||
h2 { color: #38bdf8; font-size: 1.1rem; margin: 2rem 0 0.8rem; }
|
h2 { color: #38bdf8; font-size: 1rem; margin: 2rem 0 0.8rem; }
|
||||||
.sub { opacity: 0.55; font-size: 0.85rem; margin-bottom: 1rem; }
|
.sub { opacity: 0.55; font-size: 0.85rem; margin-bottom: 1.5rem; }
|
||||||
table { width: 100%; border-collapse: collapse; }
|
table { width: 100%; border-collapse: collapse; }
|
||||||
td, th { padding: 0.7rem; border-bottom: 1px solid #334155; text-align: left; vertical-align: middle; }
|
td, th { padding: 0.7rem; border-bottom: 1px solid #334155; text-align: left; vertical-align: middle; }
|
||||||
th { opacity: 0.7; font-weight: 600; font-size: 0.85rem; text-transform: uppercase; letter-spacing: .03em; }
|
th { opacity: 0.7; font-weight: 600; font-size: 0.85rem; text-transform: uppercase; letter-spacing: .03em; }
|
||||||
@@ -359,7 +247,6 @@ BASE_STYLE = """
|
|||||||
.btn-prune:hover { background: #5b21b6; }
|
.btn-prune:hover { background: #5b21b6; }
|
||||||
.empty { opacity: 0.6; margin-top: 1.5rem; }
|
.empty { opacity: 0.6; margin-top: 1.5rem; }
|
||||||
form { display: inline; }
|
form { display: inline; }
|
||||||
code { background: #1e293b; padding: 0.1rem 0.4rem; border-radius: 4px; font-size: 0.85rem; }
|
|
||||||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
gap: 1rem; margin-bottom: 2rem; }
|
gap: 1rem; margin-bottom: 2rem; }
|
||||||
.stat-card { background: #1e293b; border-radius: 10px; padding: 1rem 1.2rem; }
|
.stat-card { background: #1e293b; border-radius: 10px; padding: 1rem 1.2rem; }
|
||||||
@@ -370,33 +257,11 @@ BASE_STYLE = """
|
|||||||
.bar-fill { height: 100%; background: #38bdf8; }
|
.bar-fill { height: 100%; background: #38bdf8; }
|
||||||
.bar-fill.warn { background: #f59e0b; }
|
.bar-fill.warn { background: #f59e0b; }
|
||||||
.bar-fill.crit { background: #ef4444; }
|
.bar-fill.crit { background: #ef4444; }
|
||||||
nav { margin-bottom: 1.5rem; display: flex; gap: 1.2rem; border-bottom: 1px solid #334155;
|
.prune-actions { display: flex; gap: 0.6rem; margin-top: 0.8rem; }
|
||||||
padding-bottom: 0.8rem; }
|
</style>
|
||||||
nav a { font-size: 0.9rem; opacity: 0.65; }
|
|
||||||
nav a.active { opacity: 1; color: #38bdf8; font-weight: 600; }
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAV = """
|
|
||||||
<nav>
|
|
||||||
<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 & Disk</a>
|
|
||||||
</nav>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CONTAINERS_TEMPLATE = """
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Deploy Panel</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="refresh" content="20">
|
|
||||||
<style>""" + BASE_STYLE + """</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Deploy Panel</h1>
|
<h1>Deploy Panel</h1>
|
||||||
""" + NAV + """
|
|
||||||
<div class="sub">{{ containers|length }} container · 20 saniyede bir otomatik yenilenir · <a href="/">şimdi yenile</a></div>
|
<div class="sub">{{ containers|length }} container · 20 saniyede bir otomatik yenilenir · <a href="/">şimdi yenile</a></div>
|
||||||
|
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
@@ -459,7 +324,7 @@ CONTAINERS_TEMPLATE = """
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="post" action="/delete/{{ c.name }}"
|
<form method="post" action="/delete/{{ c.name }}"
|
||||||
onsubmit="return confirm('{{ c.name }} silinsin mi?{{ ' DNS kaydı da kaldırılacak.' if c.hostname else '' }}');">
|
onsubmit="return confirm('{{ c.name }} silinsin mi?{{ ' DNS kaydı da otomatik kaldırılacak.' if c.hostname else '' }}');">
|
||||||
<button class="btn-delete" type="submit">Sil</button>
|
<button class="btn-delete" type="submit">Sil</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -470,23 +335,8 @@ CONTAINERS_TEMPLATE = """
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p class="empty">Hiç container yok.</p>
|
<p class="empty">Hiç container yok.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
SYSTEM_TEMPLATE = """
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Sistem & Disk - Deploy Panel</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<style>""" + BASE_STYLE + """</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Deploy Panel</h1>
|
|
||||||
""" + NAV + """
|
|
||||||
<div class="sub">Docker'ın diskte kapladığı alan ve imaj/volume yönetimi</div>
|
|
||||||
|
|
||||||
|
<h2>Disk Kullanımı</h2>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>Tür</th><th>Toplam</th><th>Kullanımda</th><th>Boyut</th><th>Geri kazanılabilir</th></tr>
|
<tr><th>Tür</th><th>Toplam</th><th>Kullanımda</th><th>Boyut</th><th>Geri kazanılabilir</th></tr>
|
||||||
{% for row in df %}
|
{% for row in df %}
|
||||||
@@ -499,159 +349,14 @@ SYSTEM_TEMPLATE = """
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
<div class="prune-actions">
|
||||||
<h2>İmajlar ({{ images|length }})</h2>
|
<form method="post" action="/images/prune" onsubmit="return confirm('Hiçbir container tarafından kullanılmayan tüm imajlar silinecek. Emin misin?');">
|
||||||
<form method="post" action="/images/prune" onsubmit="return confirm('Hiçbir container tarafından kullanılmayan TÜM imajlar silinecek. Emin misin?');">
|
<button class="btn-prune" type="submit">Kullanılmayan imajları temizle</button>
|
||||||
<button class="btn-prune" type="submit">Kullanılmayan imajları temizle</button>
|
</form>
|
||||||
</form>
|
<form method="post" action="/volumes/prune" onsubmit="return confirm('Hiçbir container tarafından kullanılmayan tüm volume\\'ler silinecek. Emin misin?');">
|
||||||
{% if images %}
|
<button class="btn-prune" type="submit">Kullanılmayan volume'leri temizle</button>
|
||||||
<table>
|
</form>
|
||||||
<tr><th>İmaj</th><th>Boyut</th><th>Oluşturulma</th><th></th></tr>
|
</div>
|
||||||
{% for img in images %}
|
|
||||||
<tr>
|
|
||||||
<td><code>{{ img.label }}</code></td>
|
|
||||||
<td>{{ img.size }}</td>
|
|
||||||
<td>{{ img.created }}</td>
|
|
||||||
<td>
|
|
||||||
<form method="post" action="/images/delete/{{ img.id }}"
|
|
||||||
onsubmit="return confirm('{{ img.label }} silinsin mi? Kullanan bir container varsa silme başarısız olur.');">
|
|
||||||
<button class="btn-delete" type="submit">Sil</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
<p class="empty">Hiç imaj yok.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h2>Volume'ler ({{ volumes|length }})</h2>
|
|
||||||
<form method="post" action="/volumes/prune" onsubmit="return confirm('Hiçbir container tarafından kullanılmayan TÜM volume\\'ler silinecek. Emin misin?');">
|
|
||||||
<button class="btn-prune" type="submit">Kullanılmayan volume'leri temizle</button>
|
|
||||||
</form>
|
|
||||||
{% if volumes %}
|
|
||||||
<table>
|
|
||||||
<tr><th>İsim</th><th>Driver</th><th></th></tr>
|
|
||||||
{% for v in volumes %}
|
|
||||||
<tr>
|
|
||||||
<td><code>{{ v.name }}</code></td>
|
|
||||||
<td>{{ v.driver }}</td>
|
|
||||||
<td>
|
|
||||||
<form method="post" action="/volumes/delete/{{ v.name }}"
|
|
||||||
onsubmit="return confirm('{{ v.name }} silinsin mi? Kullanan bir container varsa silme başarısız olur.');">
|
|
||||||
<button class="btn-delete" type="submit">Sil</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
<p class="empty">Hiç volume yok.</p>
|
|
||||||
{% endif %}
|
|
||||||
</body>
|
|
||||||
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
@@ -682,59 +387,10 @@ LOGS_TEMPLATE = """
|
|||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template_string(
|
return render_template_string(
|
||||||
CONTAINERS_TEMPLATE, containers=list_all_containers(), s=server_stats(), page="containers"
|
TEMPLATE, containers=list_all_containers(), s=server_stats(), df=docker_system_df()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/system")
|
|
||||||
def system_ui():
|
|
||||||
return render_template_string(
|
|
||||||
SYSTEM_TEMPLATE, df=docker_system_df(), images=list_images(), volumes=list_volumes(), page="system"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@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(
|
||||||
@@ -748,19 +404,19 @@ def logs_ui(name):
|
|||||||
@app.route("/start/<name>", methods=["POST"])
|
@app.route("/start/<name>", methods=["POST"])
|
||||||
def start_ui(name):
|
def start_ui(name):
|
||||||
subprocess.run(["docker", "start", name])
|
subprocess.run(["docker", "start", name])
|
||||||
return redirect(request.referrer or "/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/stop/<name>", methods=["POST"])
|
@app.route("/stop/<name>", methods=["POST"])
|
||||||
def stop_ui(name):
|
def stop_ui(name):
|
||||||
subprocess.run(["docker", "stop", name])
|
subprocess.run(["docker", "stop", name])
|
||||||
return redirect(request.referrer or "/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/restart/<name>", methods=["POST"])
|
@app.route("/restart/<name>", methods=["POST"])
|
||||||
def restart_ui(name):
|
def restart_ui(name):
|
||||||
subprocess.run(["docker", "restart", name])
|
subprocess.run(["docker", "restart", name])
|
||||||
return redirect(request.referrer or "/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/delete/<name>", methods=["POST"])
|
@app.route("/delete/<name>", methods=["POST"])
|
||||||
@@ -768,31 +424,19 @@ def delete_ui(name):
|
|||||||
hostname = find_container_hostname(name)
|
hostname = find_container_hostname(name)
|
||||||
subprocess.run(["docker", "rm", "-f", name])
|
subprocess.run(["docker", "rm", "-f", name])
|
||||||
delete_dns_record(hostname)
|
delete_dns_record(hostname)
|
||||||
return redirect(request.referrer or "/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/images/delete/<image_id>", methods=["POST"])
|
|
||||||
def delete_image(image_id):
|
|
||||||
subprocess.run(["docker", "rmi", image_id])
|
|
||||||
return redirect("/system")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/images/prune", methods=["POST"])
|
@app.route("/images/prune", methods=["POST"])
|
||||||
def prune_images():
|
def prune_images():
|
||||||
subprocess.run(["docker", "image", "prune", "-af"])
|
subprocess.run(["docker", "image", "prune", "-af"])
|
||||||
return redirect("/system")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/volumes/delete/<name>", methods=["POST"])
|
|
||||||
def delete_volume(name):
|
|
||||||
subprocess.run(["docker", "volume", "rm", name])
|
|
||||||
return redirect("/system")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/volumes/prune", methods=["POST"])
|
@app.route("/volumes/prune", methods=["POST"])
|
||||||
def prune_volumes():
|
def prune_volumes():
|
||||||
subprocess.run(["docker", "volume", "prune", "-f"])
|
subprocess.run(["docker", "volume", "prune", "-f"])
|
||||||
return redirect("/system")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/ensure-dns", methods=["POST"])
|
@app.route("/api/ensure-dns", methods=["POST"])
|
||||||
|
|||||||
Reference in New Issue
Block a user