chore: интерфесы бота

This commit is contained in:
protokey committed 2026-09-24 17:33:10 +04:00
1 parent 5f53cadf89
commit 6d322f36a9
42 files changed
+1731 -111

No files matched your search

+91
View File
@@ -1,5 +1,7 @@
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
from django.db import models
from serverus import Host
class UserManager(BaseUserManager):
@@ -46,3 +48,92 @@ class User(AbstractUser):
def email_user(self, *args, **kwargs):
raise NotImplementedError('User has no email')
class Point(models.Model):
"""Точка доступа: кластер из сервера с панелью 3x-ui и сервера маскировки перед ним.
Клиенты подключаются к серверу маскировки, он перенаправляет трафик на панель и скрывает ее адрес.
"""
CF_WARP = 'cf_warp'
class Status(models.TextChoices):
INSTALLING = 'installing', 'устанавливается'
READY = 'ready', 'активен'
ERROR = 'error', 'ошибка'
RELEASING = 'releasing', 'возвращается'
owner = models.ForeignKey('Telegram.User', on_delete=models.PROTECT, related_name='points')
name = models.CharField(max_length=64, unique=True)
status = models.CharField(max_length=16, choices=Status.choices, default=Status.INSTALLING)
error = models.TextField(blank=True)
domain = models.CharField(max_length=253, blank=True)
cloudflare = models.BooleanField(default=False, help_text='Домен спрятан за Cloudflare')
outbound_proxy = models.CharField(
max_length=512, blank=True, help_text=f'protocol://user:password@host:port или {CF_WARP}',
)
panel_username = models.CharField(max_length=64)
panel_password = models.CharField(max_length=128)
panel_port = models.PositiveIntegerField()
panel_path = models.CharField(max_length=64)
panel_api_token = models.CharField(max_length=255, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = 'точка доступа'
verbose_name_plural = 'точки доступа'
def __str__(self):
return self.name
@property
def is_busy(self) -> bool:
return self.status in (self.Status.INSTALLING, self.Status.RELEASING)
def server(self, role: str) -> 'Server':
return self.servers.get(role=role)
@property
def panel(self) -> 'Server':
return self.server(Server.Role.PANEL)
@property
def mask(self) -> 'Server':
return self.server(Server.Role.MASK)
class Server(models.Model):
"""Сервер под управлением системы. Поля ssh отражают текущее состояние сервера."""
class Role(models.TextChoices):
PANEL = 'panel', 'панель'
MASK = 'mask', 'маскировка'
point = models.ForeignKey(Point, on_delete=models.CASCADE, related_name='servers')
role = models.CharField(max_length=8, choices=Role.choices)
address = models.GenericIPAddressField(unique=True)
ssh_port = models.PositiveIntegerField(default=22)
root_password = models.CharField(max_length=128, help_text='Текущий пароль root')
private_key = models.TextField(blank=True)
public_key = models.TextField(blank=True)
known_hosts = models.TextField(blank=True, help_text='Отпечаток ssh сервера. Сбрасывается после переустановки ОС')
uname = models.CharField(max_length=512, blank=True)
class Meta:
verbose_name = 'сервер'
verbose_name_plural = 'серверы'
constraints = [models.UniqueConstraint(fields=['point', 'role'], name='app_server_role_unique')]
def __str__(self):
return f'{self.address} ({self.get_role_display()})'
def host(self) -> Host:
return Host(self.address, port=self.ssh_port, private_key=self.private_key or None, known_hosts=self.known_hosts)
def reset_fingerprint(self):
self.known_hosts = ''
self.save(update_fields=['known_hosts'])