diff --git a/api/__pycache__/settings.cpython-313.pyc b/api/__pycache__/settings.cpython-313.pyc index 8e733ec7..f4892180 100644 Binary files a/api/__pycache__/settings.cpython-313.pyc and b/api/__pycache__/settings.cpython-313.pyc differ diff --git a/api/__pycache__/urls.cpython-313.pyc b/api/__pycache__/urls.cpython-313.pyc index c4a84014..281cc9c3 100644 Binary files a/api/__pycache__/urls.cpython-313.pyc and b/api/__pycache__/urls.cpython-313.pyc differ diff --git a/api/urls.py b/api/urls.py index fd5be2eb..1fe4b0b0 100644 --- a/api/urls.py +++ b/api/urls.py @@ -14,7 +14,7 @@ Including another URLconf 2. Add a URL to urlpatterns: re_path('blog/', include('blog.urls')) """ from django.urls import re_path -from app import views, cob, email, backup, server, importdata +from app import views,hetzner, cob, email, backup, server, importdata urlpatterns = [ @@ -46,9 +46,11 @@ urlpatterns = [ re_path('set-token-expiry/', views.set_token_expiry), re_path('download-contract/(?P.+)', views.download_contract), re_path('execute-command/$', server.execute_command), + re_path('hetzner/(?P[\w]+)/$', hetzner.do_hetzner), re_path('generate-document/$',views.generate_document), re_path('model-fields/(?P.+)/', importdata.model_fields), re_path('read-excel/', importdata.read_excel), re_path('find-key/$', importdata.find_key), re_path('email-preview/$', views.preview_email_template) + ] \ No newline at end of file diff --git a/app/__pycache__/email.cpython-313.pyc b/app/__pycache__/email.cpython-313.pyc index ffc70894..727a8591 100644 Binary files a/app/__pycache__/email.cpython-313.pyc and b/app/__pycache__/email.cpython-313.pyc differ diff --git a/app/hetzner.py b/app/hetzner.py new file mode 100644 index 00000000..a505eff3 --- /dev/null +++ b/app/hetzner.py @@ -0,0 +1,69 @@ +# hetzner.py +from __future__ import annotations +import json +from os import environ +from dotenv import load_dotenv +from hcloud import Client +from django.http import JsonResponse +from django.views.decorators.csrf import csrf_exempt + +load_dotenv() + +def get_client(): + return Client(token=environ["HCLOUD_TOKEN"]) + +# ── router ────────────────────────────────────────────────────────────────── + +@csrf_exempt +def do_hetzner(request, action): + routes = { + "get_server_types": get_server_types, + } + + handler = routes.get(action) + if handler is None: + return JsonResponse({"error": f"Action '{action}' not found"}, status=404) + + try: + body = json.loads(request.body) if request.body else {} + except json.JSONDecodeError: + return JsonResponse({"error": "Invalid JSON body"}, status=400) + + return handler(request, body) + +# ── handlers ───────────────────────────────────────────────────────────────── + +def get_server_types(request, body): + client = get_client() + server_types = client.server_types.get_all() + + def serialize_server_type(st): + dm = st.data_model + return { + "id": dm.id, + "name": dm.name, + "description": dm.description, + "category": dm.category, + "cores": dm.cores, + "memory": dm.memory, + "disk": dm.disk, + "storage_type": dm.storage_type, + "cpu_type": dm.cpu_type, + "architecture": dm.architecture, + "deprecated": dm.deprecated, + "prices": dm.prices, + "locations": [ + { + "id": loc.location.id, + "name": loc.location.name, + "deprecation": { + "announced": loc.deprecation.announced.isoformat(), + "unavailable_after": loc.deprecation.unavailable_after.isoformat(), + } if loc.deprecation else None, + } + for loc in dm.locations + ], + } + + result = [serialize_server_type(st) for st in server_types] + return JsonResponse(result, safe=False) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 47d97af9..1c5b5c55 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,4 +22,6 @@ paramiko channels prefect croniter -uvicorn[standard] \ No newline at end of file +uvicorn[standard] +hcloud +python-dotenv \ No newline at end of file