Compare commits
3 Commits
221dc0f1e1
...
origin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4acaa0bc3c | ||
|
|
52977d0104 | ||
|
|
dfc7914d3d |
1
.env
Normal file
1
.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
HCLOUD_TOKEN=RvGyXJLLmgGCnWM4EOZwFMYK4xfGvwmNCbkm8G1NzIp9PZSyOf65PW6Dvy7ebcvP
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,7 +14,7 @@ Including another URLconf
|
|||||||
2. Add a URL to urlpatterns: re_path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: re_path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.urls import re_path
|
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 = [
|
urlpatterns = [
|
||||||
|
|
||||||
@@ -46,9 +46,11 @@ urlpatterns = [
|
|||||||
re_path('set-token-expiry/', views.set_token_expiry),
|
re_path('set-token-expiry/', views.set_token_expiry),
|
||||||
re_path('download-contract/(?P<name>.+)', views.download_contract),
|
re_path('download-contract/(?P<name>.+)', views.download_contract),
|
||||||
re_path('execute-command/$', server.execute_command),
|
re_path('execute-command/$', server.execute_command),
|
||||||
|
re_path('product/(?P<action>[\w]+)/$', hetzner.do_hetzner),
|
||||||
re_path('generate-document/$',views.generate_document),
|
re_path('generate-document/$',views.generate_document),
|
||||||
re_path('model-fields/(?P<name>.+)/', importdata.model_fields),
|
re_path('model-fields/(?P<name>.+)/', importdata.model_fields),
|
||||||
re_path('read-excel/', importdata.read_excel),
|
re_path('read-excel/', importdata.read_excel),
|
||||||
re_path('find-key/$', importdata.find_key),
|
re_path('find-key/$', importdata.find_key),
|
||||||
re_path('email-preview/$', views.preview_email_template)
|
re_path('email-preview/$', views.preview_email_template)
|
||||||
|
|
||||||
]
|
]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
69
app/hetzner.py
Normal file
69
app/hetzner.py
Normal file
@@ -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)
|
||||||
35
app/migrations/0011_webadmin_setting.py
Normal file
35
app/migrations/0011_webadmin_setting.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 5.1.7 on 2026-04-02 07:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0010_category_data_story_category'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Webadmin_Setting',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('category', models.CharField(max_length=100)),
|
||||||
|
('classify', models.CharField(max_length=100)),
|
||||||
|
('code', models.CharField(max_length=100)),
|
||||||
|
('vi', models.TextField()),
|
||||||
|
('en', models.TextField(null=True)),
|
||||||
|
('image', models.TextField(null=True)),
|
||||||
|
('icon', models.TextField(null=True)),
|
||||||
|
('link', models.TextField(null=True)),
|
||||||
|
('detail', models.JSONField(null=True)),
|
||||||
|
('detail_en', models.JSONField(null=True)),
|
||||||
|
('index', models.IntegerField(default=0, null=True)),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'webadmin_setting',
|
||||||
|
'unique_together': {('category', 'classify', 'code')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -889,6 +889,25 @@ class Data_Story(models.Model):
|
|||||||
db_table = 'data_story'
|
db_table = 'data_story'
|
||||||
|
|
||||||
|
|
||||||
|
class Webadmin_Setting(models.Model):
|
||||||
|
category = models.CharField(max_length=100, null=False)
|
||||||
|
classify = models.CharField(max_length=100, null=False)
|
||||||
|
code = models.CharField(max_length=100, null = False)
|
||||||
|
vi = models.TextField(null=False)
|
||||||
|
en = models.TextField(null=True)
|
||||||
|
image = models.TextField(null=True)
|
||||||
|
icon = models.TextField(null=True)
|
||||||
|
link = models.TextField(null=True)
|
||||||
|
detail = models.JSONField(null=True)
|
||||||
|
detail_en = models.JSONField(null=True)
|
||||||
|
index = models.IntegerField(null=True, default=0)
|
||||||
|
create_time = models.DateTimeField(null=True, auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'webadmin_setting'
|
||||||
|
unique_together = ('category', 'classify', 'code')
|
||||||
|
|
||||||
|
|
||||||
class Company(AutoCodeModel):
|
class Company(AutoCodeModel):
|
||||||
code_prefix = "CP"
|
code_prefix = "CP"
|
||||||
code_padding = 5
|
code_padding = 5
|
||||||
|
|||||||
@@ -22,4 +22,6 @@ paramiko
|
|||||||
channels
|
channels
|
||||||
prefect
|
prefect
|
||||||
croniter
|
croniter
|
||||||
uvicorn[standard]
|
uvicorn[standard]
|
||||||
|
hcloud
|
||||||
|
python-dotenv
|
||||||
Reference in New Issue
Block a user