Add Coolify domain sync for system admin
This commit is contained in:
@@ -48,3 +48,10 @@ JWT_ISSUER="audit_firm_v2"
|
|||||||
JWT_AUDIENCE="audit_firm_clients"
|
JWT_AUDIENCE="audit_firm_clients"
|
||||||
JWT_ACCESS_MINUTES=15
|
JWT_ACCESS_MINUTES=15
|
||||||
JWT_REFRESH_DAYS=30
|
JWT_REFRESH_DAYS=30
|
||||||
|
|
||||||
|
# Coolify API integration for System Admin domain sync
|
||||||
|
# Enable API in Coolify first and create a token with write permission.
|
||||||
|
COOLIFY_API_BASE_URL="https://coolify.example.com/api/v1"
|
||||||
|
COOLIFY_API_TOKEN=""
|
||||||
|
COOLIFY_ERP_APPLICATION_UUID=""
|
||||||
|
COOLIFY_VERIFY_TLS=true
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import ssl
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import Any
|
||||||
|
from urllib.error import HTTPError, URLError
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.modules.domain_management.models import DomainMapping
|
||||||
|
from app.modules.domain_management.services import normalize_domain
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CoolifyConfig:
|
||||||
|
api_base_url: str
|
||||||
|
api_token: str
|
||||||
|
application_uuid: str
|
||||||
|
verify_tls: bool = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_configured(self) -> bool:
|
||||||
|
return bool(self.api_base_url and self.api_token and self.application_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CoolifyDomainSyncResult:
|
||||||
|
ok: bool
|
||||||
|
status: str
|
||||||
|
message: str
|
||||||
|
domain_url: str | None = None
|
||||||
|
existing_domains: tuple[str, ...] = ()
|
||||||
|
updated_domains: tuple[str, ...] = ()
|
||||||
|
api_status_code: int | None = None
|
||||||
|
api_response: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_coolify_config() -> CoolifyConfig:
|
||||||
|
"""Read Coolify API configuration from environment.
|
||||||
|
|
||||||
|
Required environment variables:
|
||||||
|
COOLIFY_API_BASE_URL Example: https://coolify.example.com/api/v1
|
||||||
|
COOLIFY_API_TOKEN Bearer token created in Coolify
|
||||||
|
COOLIFY_ERP_APPLICATION_UUID Coolify ERP application UUID
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
COOLIFY_VERIFY_TLS=false Only for private/self-signed Coolify API endpoints
|
||||||
|
"""
|
||||||
|
base = (os.getenv("COOLIFY_API_BASE_URL") or os.getenv("COOLIFY_API_URL") or "").strip().rstrip("/")
|
||||||
|
token = (os.getenv("COOLIFY_API_TOKEN") or "").strip()
|
||||||
|
app_uuid = (os.getenv("COOLIFY_ERP_APPLICATION_UUID") or os.getenv("COOLIFY_APPLICATION_UUID") or "").strip()
|
||||||
|
verify_tls_raw = (os.getenv("COOLIFY_VERIFY_TLS") or "true").strip().lower()
|
||||||
|
verify_tls = verify_tls_raw not in {"0", "false", "no", "off"}
|
||||||
|
return CoolifyConfig(api_base_url=base, api_token=token, application_uuid=app_uuid, verify_tls=verify_tls)
|
||||||
|
|
||||||
|
|
||||||
|
def is_coolify_configured() -> bool:
|
||||||
|
return get_coolify_config().is_configured
|
||||||
|
|
||||||
|
|
||||||
|
def _normalise_api_base_url(value: str) -> str:
|
||||||
|
base = (value or "").strip().rstrip("/")
|
||||||
|
if not base:
|
||||||
|
return ""
|
||||||
|
if not base.endswith("/api/v1"):
|
||||||
|
base = base + "/api/v1"
|
||||||
|
return base + "/"
|
||||||
|
|
||||||
|
|
||||||
|
def _request_json(config: CoolifyConfig, method: str, endpoint: str, payload: dict[str, Any] | None = None) -> tuple[int, dict[str, Any], str]:
|
||||||
|
base = _normalise_api_base_url(config.api_base_url)
|
||||||
|
url = urljoin(base, endpoint.lstrip("/"))
|
||||||
|
body: bytes | None = None
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {config.api_token}",
|
||||||
|
"Accept": "application/json",
|
||||||
|
}
|
||||||
|
if payload is not None:
|
||||||
|
body = json.dumps(payload).encode("utf-8")
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
req = Request(url=url, data=body, method=method.upper(), headers=headers)
|
||||||
|
context = None if config.verify_tls else ssl._create_unverified_context() # noqa: SLF001 - explicit opt-out via env only
|
||||||
|
try:
|
||||||
|
with urlopen(req, timeout=25, context=context) as response: # nosec B310 - endpoint is admin-configured
|
||||||
|
raw = response.read().decode("utf-8", errors="replace")
|
||||||
|
try:
|
||||||
|
data = json.loads(raw) if raw else {}
|
||||||
|
except Exception:
|
||||||
|
data = {"raw": raw}
|
||||||
|
return int(response.status), data, raw
|
||||||
|
except HTTPError as exc:
|
||||||
|
raw = exc.read().decode("utf-8", errors="replace")
|
||||||
|
try:
|
||||||
|
data = json.loads(raw) if raw else {}
|
||||||
|
except Exception:
|
||||||
|
data = {"raw": raw}
|
||||||
|
return int(exc.code), data, raw
|
||||||
|
except URLError as exc:
|
||||||
|
raise RuntimeError(f"Coolify API connection failed: {exc.reason}") from exc
|
||||||
|
except TimeoutError as exc:
|
||||||
|
raise RuntimeError("Coolify API request timed out.") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def _split_domain_urls(value: str | None) -> tuple[str, ...]:
|
||||||
|
if not value:
|
||||||
|
return ()
|
||||||
|
seen: set[str] = set()
|
||||||
|
out: list[str] = []
|
||||||
|
for part in value.split(","):
|
||||||
|
item = part.strip().rstrip("/")
|
||||||
|
if not item:
|
||||||
|
continue
|
||||||
|
key = item.lower()
|
||||||
|
if key not in seen:
|
||||||
|
seen.add(key)
|
||||||
|
out.append(item)
|
||||||
|
return tuple(out)
|
||||||
|
|
||||||
|
|
||||||
|
def _domain_to_coolify_url(domain_name: str) -> str:
|
||||||
|
domain = normalize_domain(domain_name)
|
||||||
|
if not domain:
|
||||||
|
return ""
|
||||||
|
return f"https://{domain}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_coolify_application_domains() -> CoolifyDomainSyncResult:
|
||||||
|
config = get_coolify_config()
|
||||||
|
if not config.is_configured:
|
||||||
|
return CoolifyDomainSyncResult(
|
||||||
|
ok=False,
|
||||||
|
status="not_configured",
|
||||||
|
message="Coolify API is not configured. Set COOLIFY_API_BASE_URL, COOLIFY_API_TOKEN and COOLIFY_ERP_APPLICATION_UUID.",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
status_code, data, raw = _request_json(config, "GET", f"applications/{config.application_uuid}")
|
||||||
|
except Exception as exc:
|
||||||
|
return CoolifyDomainSyncResult(ok=False, status="api_error", message=str(exc))
|
||||||
|
if status_code < 200 or status_code >= 300:
|
||||||
|
return CoolifyDomainSyncResult(
|
||||||
|
ok=False,
|
||||||
|
status="api_error",
|
||||||
|
message=f"Coolify returned HTTP {status_code} while reading the application.",
|
||||||
|
api_status_code=status_code,
|
||||||
|
api_response=raw[:1000],
|
||||||
|
)
|
||||||
|
domains = _split_domain_urls(str(data.get("fqdn") or data.get("domains") or ""))
|
||||||
|
return CoolifyDomainSyncResult(
|
||||||
|
ok=True,
|
||||||
|
status="read_ok",
|
||||||
|
message="Coolify application domains were read successfully.",
|
||||||
|
existing_domains=domains,
|
||||||
|
updated_domains=domains,
|
||||||
|
api_status_code=status_code,
|
||||||
|
api_response=raw[:1000],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_domain_to_coolify(db: Session, mapping: DomainMapping, *, user_id: int | None = None) -> CoolifyDomainSyncResult:
|
||||||
|
"""Add the mapping domain to the configured Coolify ERP application domains.
|
||||||
|
|
||||||
|
This function deliberately does not create/update ERP domain mappings. It only updates
|
||||||
|
the Coolify application domain list and records proxy/SSL status on the existing mapping.
|
||||||
|
"""
|
||||||
|
config = get_coolify_config()
|
||||||
|
domain_url = _domain_to_coolify_url(mapping.domain_name)
|
||||||
|
if not domain_url:
|
||||||
|
return CoolifyDomainSyncResult(ok=False, status="invalid_domain", message="Domain name is empty or invalid.")
|
||||||
|
if not config.is_configured:
|
||||||
|
return CoolifyDomainSyncResult(
|
||||||
|
ok=False,
|
||||||
|
status="not_configured",
|
||||||
|
message="Coolify API is not configured. Set COOLIFY_API_BASE_URL, COOLIFY_API_TOKEN and COOLIFY_ERP_APPLICATION_UUID.",
|
||||||
|
domain_url=domain_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
read_result = get_coolify_application_domains()
|
||||||
|
if not read_result.ok:
|
||||||
|
return CoolifyDomainSyncResult(
|
||||||
|
ok=False,
|
||||||
|
status=read_result.status,
|
||||||
|
message=read_result.message,
|
||||||
|
domain_url=domain_url,
|
||||||
|
api_status_code=read_result.api_status_code,
|
||||||
|
api_response=read_result.api_response,
|
||||||
|
)
|
||||||
|
|
||||||
|
existing = list(read_result.updated_domains)
|
||||||
|
existing_keys = {d.lower().rstrip("/") for d in existing}
|
||||||
|
if domain_url.lower() in existing_keys:
|
||||||
|
mapping.ssl_mode = "coolify"
|
||||||
|
mapping.ssl_provider = "coolify"
|
||||||
|
mapping.ssl_status = mapping.ssl_status or "pending_ssl"
|
||||||
|
mapping.ssl_last_error = None
|
||||||
|
mapping.updated_by_user_id = user_id
|
||||||
|
mapping.updated_at_utc = datetime.now(timezone.utc)
|
||||||
|
db.commit()
|
||||||
|
return CoolifyDomainSyncResult(
|
||||||
|
ok=True,
|
||||||
|
status="already_present",
|
||||||
|
message="Domain is already present in Coolify application domains.",
|
||||||
|
domain_url=domain_url,
|
||||||
|
existing_domains=tuple(existing),
|
||||||
|
updated_domains=tuple(existing),
|
||||||
|
)
|
||||||
|
|
||||||
|
updated = existing + [domain_url]
|
||||||
|
domains_csv = ",".join(updated)
|
||||||
|
|
||||||
|
# Current Coolify application API writes domain URLs through the request field named "domains".
|
||||||
|
# Some older builds used/accepted fqdn, so we fallback only if the first attempt is rejected.
|
||||||
|
patch_attempts = ({"domains": domains_csv}, {"fqdn": domains_csv})
|
||||||
|
last_status = None
|
||||||
|
last_raw = ""
|
||||||
|
for payload in patch_attempts:
|
||||||
|
try:
|
||||||
|
status_code, _data, raw = _request_json(config, "PATCH", f"applications/{config.application_uuid}", payload)
|
||||||
|
except Exception as exc:
|
||||||
|
return CoolifyDomainSyncResult(ok=False, status="api_error", message=str(exc), domain_url=domain_url)
|
||||||
|
last_status = status_code
|
||||||
|
last_raw = raw
|
||||||
|
if 200 <= status_code < 300:
|
||||||
|
mapping.ssl_mode = "coolify"
|
||||||
|
mapping.ssl_provider = "coolify"
|
||||||
|
mapping.ssl_status = "pending_ssl"
|
||||||
|
mapping.ssl_last_error = None
|
||||||
|
mapping.updated_by_user_id = user_id
|
||||||
|
mapping.updated_at_utc = datetime.now(timezone.utc)
|
||||||
|
db.commit()
|
||||||
|
return CoolifyDomainSyncResult(
|
||||||
|
ok=True,
|
||||||
|
status="synced",
|
||||||
|
message="Domain was added to Coolify application domains. Redeploy/restart the ERP app if your Coolify version does not refresh labels immediately.",
|
||||||
|
domain_url=domain_url,
|
||||||
|
existing_domains=tuple(existing),
|
||||||
|
updated_domains=tuple(updated),
|
||||||
|
api_status_code=status_code,
|
||||||
|
api_response=raw[:1000],
|
||||||
|
)
|
||||||
|
|
||||||
|
mapping.ssl_mode = "coolify"
|
||||||
|
mapping.ssl_provider = "coolify"
|
||||||
|
mapping.ssl_status = "failed"
|
||||||
|
mapping.ssl_last_error = f"Coolify API returned HTTP {last_status}: {last_raw[:500]}"
|
||||||
|
mapping.updated_by_user_id = user_id
|
||||||
|
mapping.updated_at_utc = datetime.now(timezone.utc)
|
||||||
|
db.commit()
|
||||||
|
return CoolifyDomainSyncResult(
|
||||||
|
ok=False,
|
||||||
|
status="api_error",
|
||||||
|
message=f"Coolify did not accept the domain update. HTTP {last_status}.",
|
||||||
|
domain_url=domain_url,
|
||||||
|
existing_domains=tuple(existing),
|
||||||
|
updated_domains=tuple(updated),
|
||||||
|
api_status_code=last_status,
|
||||||
|
api_response=last_raw[:1000],
|
||||||
|
)
|
||||||
@@ -67,5 +67,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if coolify_result is defined and coolify_result %}
|
||||||
|
<div class="rounded-2xl {% if coolify_result.ok %}bg-emerald-50 text-emerald-800{% else %}bg-red-50 text-red-800{% endif %} p-4 text-sm">
|
||||||
|
<div class="font-semibold">Coolify: {{ coolify_result.status.replace('_', ' ')|title }}</div>
|
||||||
|
<div class="mt-1">{{ coolify_result.message }}</div>
|
||||||
|
{% if coolify_result.domain_url %}<div class="mt-2 text-xs">Domain URL: {{ coolify_result.domain_url }}</div>{% endif %}
|
||||||
|
{% if coolify_result.updated_domains %}<div class="mt-2 text-xs">Application domains: {{ coolify_result.updated_domains|join(', ') }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="rounded-2xl border bg-white p-5 shadow-soft">
|
||||||
|
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-semibold text-slate-900">Coolify / Traefik Route</h2>
|
||||||
|
<p class="mt-1 text-sm text-slate-500">System Admin can add this custom domain to the configured Coolify ERP application. DNS should already point to your Coolify server before SSL is issued.</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 sm:flex-row">
|
||||||
|
<form method="post" action="/domains/{{ mapping.id }}/coolify/check">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||||
|
<button class="w-full rounded-xl border px-4 py-2 text-sm font-medium text-slate-700">Check Coolify App</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="/domains/{{ mapping.id }}/coolify/sync">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||||
|
<button class="w-full rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white">Add to Coolify</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 grid grid-cols-1 gap-3 text-sm md:grid-cols-3">
|
||||||
|
<div class="rounded-xl bg-slate-50 p-3"><div class="text-slate-500">Coolify URL format</div><div class="font-medium text-slate-900">https://{{ mapping.domain_name }}</div></div>
|
||||||
|
<div class="rounded-xl bg-slate-50 p-3"><div class="text-slate-500">Proxy mode</div><div class="font-medium text-slate-900">{{ mapping.ssl_mode or 'manual' }}</div></div>
|
||||||
|
<div class="rounded-xl bg-slate-50 p-3"><div class="text-slate-500">Proxy/SSL status</div><div class="font-medium text-slate-900">{{ (mapping.ssl_status or 'not_checked').replace('_', ' ')|title }}</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
{% extends "ui/templates/base/layout.html" %}
|
{% extends "ui/templates/base/layout.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
|
|
||||||
|
{% if coolify_result is defined and coolify_result %}
|
||||||
|
<div class="rounded-2xl {% if coolify_result.ok %}bg-emerald-50 text-emerald-800{% else %}bg-red-50 text-red-800{% endif %} p-4 text-sm">
|
||||||
|
<div class="font-semibold">Coolify API: {{ coolify_result.status.replace('_', ' ')|title }}</div>
|
||||||
|
<div class="mt-1">{{ coolify_result.message }}</div>
|
||||||
|
{% if coolify_result.updated_domains %}<div class="mt-2 text-xs">Application domains: {{ coolify_result.updated_domains|join(', ') }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
|
<div class="flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-semibold text-slate-900">SSL Automation</h1>
|
<h1 class="text-2xl font-semibold text-slate-900">SSL Automation</h1>
|
||||||
@@ -11,6 +19,7 @@
|
|||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white shadow-sm">Check Active SSL</button>
|
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white shadow-sm">Check Active SSL</button>
|
||||||
</form>
|
</form>
|
||||||
|
<a href="/domains/coolify/status" class="rounded-xl border px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Coolify Status</a>
|
||||||
<a href="/domains/verification" class="rounded-xl border px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">DNS Verification</a>
|
<a href="/domains/verification" class="rounded-xl border px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">DNS Verification</a>
|
||||||
<a href="/domains" class="rounded-xl border px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Domains</a>
|
<a href="/domains" class="rounded-xl border px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Domains</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,6 +34,11 @@
|
|||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white">Check SSL Now</button>
|
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white">Check SSL Now</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="/domains/{{ mapping.id }}/coolify/sync">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||||
|
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white">Add Domain to Coolify</button>
|
||||||
|
</form>
|
||||||
<form method="post" action="/domains/{{ mapping.id }}/ssl/mark-managed" class="flex gap-2">
|
<form method="post" action="/domains/{{ mapping.id }}/ssl/mark-managed" class="flex gap-2">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||||
<select name="provider" class="rounded-xl border px-3 py-2 text-sm">
|
<select name="provider" class="rounded-xl border px-3 py-2 text-sm">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from app.core.security.session_auth import get_current_user
|
|||||||
from app.core.templating import templates
|
from app.core.templating import templates
|
||||||
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
||||||
from app.modules.domain_management.models import DomainMapping
|
from app.modules.domain_management.models import DomainMapping
|
||||||
|
from app.modules.domain_management.coolify_client import get_coolify_application_domains, sync_domain_to_coolify
|
||||||
from app.modules.domain_management.services import (
|
from app.modules.domain_management.services import (
|
||||||
DomainMappingPayload,
|
DomainMappingPayload,
|
||||||
create_domain_mapping,
|
create_domain_mapping,
|
||||||
@@ -38,7 +39,7 @@ router = APIRouter(prefix="/domains", tags=["domain-management-ui"])
|
|||||||
|
|
||||||
def _is_domain_admin(db, user) -> bool:
|
def _is_domain_admin(db, user) -> bool:
|
||||||
roles = set(get_user_roles(db, user.id))
|
roles = set(get_user_roles(db, user.id))
|
||||||
return bool(roles.intersection({"System Admin", "Firm Admin"}))
|
return "System Admin" in roles
|
||||||
|
|
||||||
|
|
||||||
def _base_ctx(request: Request, db, user, **ctx):
|
def _base_ctx(request: Request, db, user, **ctx):
|
||||||
@@ -535,6 +536,24 @@ def ssl_check_all(request: Request, csrf_token: str = Form(...)):
|
|||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/coolify/status")
|
||||||
|
def coolify_status_page(request: Request):
|
||||||
|
db = CommonSessionLocal()
|
||||||
|
try:
|
||||||
|
user, response = _require_user(request, db)
|
||||||
|
if response:
|
||||||
|
return response
|
||||||
|
result = get_coolify_application_domains()
|
||||||
|
domains = list_ssl_domains(db)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"modules/domain_management/templates/domain_management/ssl.html",
|
||||||
|
_base_ctx(request, db, user, title="Coolify Domain Sync", domains=domains, coolify_result=result),
|
||||||
|
status_code=200 if result.ok else 400,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{mapping_id}/ssl")
|
@router.get("/{mapping_id}/ssl")
|
||||||
def ssl_detail(request: Request, mapping_id: int):
|
def ssl_detail(request: Request, mapping_id: int):
|
||||||
db = CommonSessionLocal()
|
db = CommonSessionLocal()
|
||||||
@@ -554,6 +573,49 @@ def ssl_detail(request: Request, mapping_id: int):
|
|||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{mapping_id}/coolify/sync")
|
||||||
|
def coolify_sync_one(request: Request, mapping_id: int, csrf_token: str = Form(...)):
|
||||||
|
validate_csrf(request, csrf_token)
|
||||||
|
db = CommonSessionLocal()
|
||||||
|
try:
|
||||||
|
user, response = _require_user(request, db)
|
||||||
|
if response:
|
||||||
|
return response
|
||||||
|
mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none()
|
||||||
|
if not mapping:
|
||||||
|
return RedirectResponse(url="/domains", status_code=303)
|
||||||
|
result = sync_domain_to_coolify(db, mapping, user_id=user.id)
|
||||||
|
db.refresh(mapping)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"modules/domain_management/templates/domain_management/detail.html",
|
||||||
|
_base_ctx(request, db, user, title=mapping.domain_name, mapping=mapping, coolify_result=result),
|
||||||
|
status_code=200 if result.ok else 400,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{mapping_id}/coolify/check")
|
||||||
|
def coolify_check_one(request: Request, mapping_id: int, csrf_token: str = Form(...)):
|
||||||
|
validate_csrf(request, csrf_token)
|
||||||
|
db = CommonSessionLocal()
|
||||||
|
try:
|
||||||
|
user, response = _require_user(request, db)
|
||||||
|
if response:
|
||||||
|
return response
|
||||||
|
mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none()
|
||||||
|
if not mapping:
|
||||||
|
return RedirectResponse(url="/domains", status_code=303)
|
||||||
|
result = get_coolify_application_domains()
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"modules/domain_management/templates/domain_management/detail.html",
|
||||||
|
_base_ctx(request, db, user, title=mapping.domain_name, mapping=mapping, coolify_result=result),
|
||||||
|
status_code=200 if result.ok else 400,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{mapping_id}/ssl/check")
|
@router.post("/{mapping_id}/ssl/check")
|
||||||
def ssl_check_one(request: Request, mapping_id: int, csrf_token: str = Form(...)):
|
def ssl_check_one(request: Request, mapping_id: int, csrf_token: str = Form(...)):
|
||||||
validate_csrf(request, csrf_token)
|
validate_csrf(request, csrf_token)
|
||||||
|
|||||||
Reference in New Issue
Block a user