Compare commits
5 Commits
63b79c95c5
...
origin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4acaa0bc3c | ||
|
|
52977d0104 | ||
|
|
dfc7914d3d | ||
|
|
221dc0f1e1 | ||
|
|
3e78dde2e2 |
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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)
|
||||||
37
app/migrations/0008_data_story.py
Normal file
37
app/migrations/0008_data_story.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 5.1.7 on 2026-04-02 02:33
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0007_alter_customer_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Data_Story',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=200)),
|
||||||
|
('subtitle', models.CharField(max_length=400, null=True)),
|
||||||
|
('image', models.TextField(null=True)),
|
||||||
|
('header', models.JSONField(null=True)),
|
||||||
|
('content', models.JSONField(null=True)),
|
||||||
|
('canonical', models.CharField(max_length=200, null=True)),
|
||||||
|
('tags', models.JSONField(null=True)),
|
||||||
|
('meta_desc', models.TextField(null=True)),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, null=True)),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='app.category')),
|
||||||
|
('language', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='app.lang_choice')),
|
||||||
|
('status', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='app.display_status')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='app.user')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'data_story',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 5.1.7 on 2026-04-02 02:36
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0008_data_story'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='data_story',
|
||||||
|
name='category',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='display_status',
|
||||||
|
name='en',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Category',
|
||||||
|
),
|
||||||
|
]
|
||||||
39
app/migrations/0010_category_data_story_category.py
Normal file
39
app/migrations/0010_category_data_story_category.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Generated by Django 5.1.7 on 2026-04-02 02:37
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0009_remove_data_story_category_remove_display_status_en_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Category',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('item', models.CharField(max_length=30, unique=True)),
|
||||||
|
('value', models.CharField(max_length=100)),
|
||||||
|
('level', models.PositiveIntegerField()),
|
||||||
|
('parent', models.CharField(max_length=30, null=True)),
|
||||||
|
('index', models.PositiveIntegerField(default=0)),
|
||||||
|
('icon', models.CharField(max_length=50, null=True)),
|
||||||
|
('image', models.CharField(max_length=500, null=True)),
|
||||||
|
('link', models.CharField(max_length=500, null=True)),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, null=True)),
|
||||||
|
('update_time', models.DateTimeField(null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'category',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='data_story',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='app.category'),
|
||||||
|
preserve_default=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')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
@@ -255,6 +255,16 @@ class Bank(models.Model):
|
|||||||
db_table = 'bank'
|
db_table = 'bank'
|
||||||
|
|
||||||
|
|
||||||
|
class Display_Status(models.Model):
|
||||||
|
code = models.CharField(max_length=30, null=False, unique=True)
|
||||||
|
name = models.CharField(max_length=100, null=False)
|
||||||
|
index = models.IntegerField(null=True, default=1)
|
||||||
|
create_time = models.DateTimeField(null=True, auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'display_status'
|
||||||
|
|
||||||
|
|
||||||
class User_Setting(models.Model):
|
class User_Setting(models.Model):
|
||||||
name = models.CharField(max_length=200, null=False, unique=True)
|
name = models.CharField(max_length=200, null=False, unique=True)
|
||||||
detail = models.JSONField(null=False)
|
detail = models.JSONField(null=False)
|
||||||
@@ -543,25 +553,6 @@ class Approve_Status(models.Model):
|
|||||||
db_table = 'approve_status'
|
db_table = 'approve_status'
|
||||||
|
|
||||||
|
|
||||||
class Category(models.Model):
|
|
||||||
code = models.CharField(max_length=30, null=False, unique=True)
|
|
||||||
name = models.CharField(max_length=100, null=False)
|
|
||||||
create_time = models.DateTimeField(null=True, auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'category'
|
|
||||||
|
|
||||||
|
|
||||||
class Display_Status(models.Model):
|
|
||||||
code = models.CharField(max_length=30, null=False, unique=True)
|
|
||||||
name = models.CharField(max_length=100, null=False)
|
|
||||||
en = models.CharField(max_length=100, null=True)
|
|
||||||
index = models.IntegerField(null=True, default=1)
|
|
||||||
create_time = models.DateTimeField(null=True, auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'display_status'
|
|
||||||
|
|
||||||
|
|
||||||
class Payment_Status(models.Model):
|
class Payment_Status(models.Model):
|
||||||
code = models.CharField(max_length=30, null=False, unique=True)
|
code = models.CharField(max_length=30, null=False, unique=True)
|
||||||
@@ -861,6 +852,62 @@ class Issued_Place(models.Model):
|
|||||||
db_table = 'issued_place'
|
db_table = 'issued_place'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Category(models.Model):
|
||||||
|
item = models.CharField(max_length=30, null=False, unique = True)
|
||||||
|
value = models.CharField(max_length=100, null=False)
|
||||||
|
level = models.PositiveIntegerField(null=False)
|
||||||
|
parent = models.CharField(max_length=30, null=True)
|
||||||
|
index = models.PositiveIntegerField(null=False, default=0)
|
||||||
|
icon = models.CharField(max_length=50, null=True)
|
||||||
|
image = models.CharField(max_length=500, null=True)
|
||||||
|
link = models.CharField(max_length=500, null=True)
|
||||||
|
create_time = models.DateTimeField(null = True, auto_now_add=True)
|
||||||
|
update_time = models.DateTimeField(null = True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'category'
|
||||||
|
|
||||||
|
|
||||||
|
class Data_Story(models.Model):
|
||||||
|
title = models.CharField(max_length=200, null=False)
|
||||||
|
subtitle = models.CharField(max_length=400, null=True)
|
||||||
|
image = models.TextField(null=True)
|
||||||
|
header = models.JSONField(null=True)
|
||||||
|
content = models.JSONField(null=True)
|
||||||
|
canonical = models.CharField(max_length=200, null=True)
|
||||||
|
category = models.ForeignKey(Category, null=False, related_name='+', on_delete=models.PROTECT)
|
||||||
|
status = models.ForeignKey(Display_Status, null=False, related_name='+', on_delete=models.PROTECT)
|
||||||
|
language = models.ForeignKey(Lang_Choice, null=False, related_name='+', on_delete=models.PROTECT)
|
||||||
|
tags = models.JSONField(null=True)
|
||||||
|
meta_desc = models.TextField(null=True)
|
||||||
|
user = models.ForeignKey(User, null=False, related_name='+', on_delete=models.PROTECT)
|
||||||
|
create_time = models.DateTimeField(null=True, auto_now_add=True)
|
||||||
|
update_time = models.DateTimeField(null=True, auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
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