Capa de Servicios
NoxPanel sigue un patrón de capa de servicios (Service Layer) que encapsula la lógica de negocio separándola de las vistas y los modelos. Cada servicio se encarga de un dominio funcional específico y coordina operaciones entre modelos Django e integraciones externas.
HostingService
Ubicación: hosting/services.py
Servicio principal para la gestión de hosting multi-tenant. Abstrae las operaciones del motor de hosting con lógica de cuotas, validación y aislamiento de tenant.
Constructor
HostingService(tenant: Optional[HostingTenant] = None)
Recibe un tenant opcional. Cuando se proporciona, todas las operaciones se
limitan a ese tenant. Si es None, se permite el modo admin.
Métodos de Gestión de Clientes
create_tenant_client(tenant, user) → ISPConfigClientCrea un cliente en el motor de hosting para un par tenant/usuario. Genera credenciales únicas con formato
{tenant.slug}_{user.username}, invocaISPConfigAPIClient.create_client()con las cuotas del tenant, y persiste el mapeo en la base de datos Django.
Métodos de Sitios Web
create_site(tenant, domain, ip_address="*") → HostingSiteCrea un sitio web verificando la cuota del tenant. Obtiene o crea el cliente del motor de hosting necesario, llama a la API del motor de hosting usando credenciales admin (
create_site_for_client), y registra el sitio en Django con el ID del recurso retornado.list_sites(tenant) → List[HostingSite]Lista todos los sitios activos del tenant.
get_site(tenant, site_id) → Optional[HostingSite]Obtiene un sitio específico con validación de pertenencia al tenant.
delete_site(tenant, site_id) → boolElimina un sitio del motor de hosting mediante la API admin y lo marca como
is_active=Falseen Django (soft-delete).request_ssl_certificate(site) → DictSolicita un certificado Let’s Encrypt para un sitio. Obtiene los datos actuales del sitio desde el motor de hosting, activa
ssl_letsencrypt=yy ejecuta un update completo (el motor de hosting requiere enviar todos los campos).
Métodos de Bases de Datos
create_database(tenant, name, db_user=None, site=None) → HostingDatabaseCrea una base de datos MySQL verificando cuotas. Genera usuario y contraseña, llama a
ISPConfigAPIClient.create_database()que primero crea el usuario de DB y luego la base de datos. Registra en Django.list_databases(tenant) → List[HostingDatabase]Lista bases de datos activas del tenant.
Workflow All-in-One: provision_full_site
provision_full_site(tenant, domain, ...) → DictWorkflow completo tipo cPanel que crea todos los recursos de un dominio en un solo paso:
Site: Crea el sitio web en el motor de hosting.
DNS Zone: Crea la zona DNS con registros por defecto (A, CNAME, MX, TXT/SPF, A para mail).
Mail Domain: Configura el dominio de email en el motor de hosting.
FTP Account: Crea cuenta FTP con usuario
{slug}_{dominio}y permisos correctos (uid/gid del motor de hosting).Database: Crea base de datos MySQL con nombre sanitizado.
Traefik Config: Genera configuración de Traefik para SSL automático.
Cada paso es independiente y tolerante a fallos: si uno falla, los demás continúan. El resultado incluye un campo
errorscon los errores ocurridos.Parámetros opcionales:
create_dns,create_mail,create_ftp,create_db,create_traefik(todosTruepor defecto).
Sincronización
sync_tenant_from_hosting_engine(tenant) → DictSincroniza recursos de un tenant desde el motor de hosting hacia Django. Consulta la API admin para obtener sitios y bases de datos del cliente del motor de hosting asociado al tenant y realiza
update_or_createen los modelos Django. Actualiza el campolast_syncdelISPConfigClient.
Métodos Helper Internos
_get_admin_client(): Obtiene instancia del cliente del motor de hosting con credenciales admin._get_tenant_client(ispconfig_client): Obtiene cliente del motor de hosting con credenciales del tenant._generate_password(length=20): Genera contraseña segura usandosecrets.choice()._get_ispconfig_client_for_tenant(tenant): Busca elISPConfigClientactivo del tenant._create_dns_zone_for_site(): Crea zona DNS local y en el motor de hosting._create_ispconfig_dns_records(): Crea registros A, MX, TXT en el motor de hosting._create_default_dns_records(): Crea registros DNS locales en Django._create_mail_domain_for_site(): Crea dominio de mail en el motor de hosting._create_default_ftp_account(): Crea cuenta FTP con uid/gid del motor de hosting._create_default_database(): Crea base de datos con nombre sanitizado.
Cliente del Motor de Hosting (API)
Ubicación: integrations/ispconfig_client.py
Cliente Python para la API JSON del motor de hosting ubicada en
/remote/json.php. Todas las operaciones usan autenticación por sesión.
Autenticación y Conexión
__init__(base_url=None, username=None, password=None)Inicializa el cliente. Si no se proporcionan credenciales, las lee de
settings.pyo del archivo compartido de contraseñas del motor de hosting.ping() → DictVerifica conectividad: realiza login y retorna
{"ok": True}._login() → strAutentica con el motor de hosting y obtiene un
session_idreutilizable. LanzaISPConfigNotConfiguredsi las credenciales faltan o son inválidas.logout() → DictCierra la sesión activa del motor de hosting.
_call(method, **params) → DictMétodo base para todas las llamadas API. Realiza
POSTa/remote/json.php?{method}con cuerpo JSON. Retorna{"ok": True, "response": ...}en éxito o{"ok": False, "error": ...}en fallo.
Gestión de Clientes
list_clients() → DictLista todos los clientes del motor de hosting con sus datos completos.
get_client(client_id) → DictObtiene información detallada de un cliente específico.
create_client(username, password, company_name, contact_name, email, **kwargs) → DictCrea un cliente con todas las cuotas configurables (web, DB, mail, FTP, DNS, shell). Si el username ya existe, busca el cliente existente.
Gestión de Sitios Web
list_sites() / list_sites_by_client(client_id) → DictListar sitios web (todos o por cliente).
create_site(domain, ip_address, **kwargs) → DictCrea sitio básico (requiere
client_iden kwargs).create_site_for_client(client_id, domain, ip_address, **kwargs) → DictVersión completa con todos los campos requeridos por el motor de hosting (PHP-FPM, subdomain www, CGI, SSI, suexec, etc.).
delete_site(site_id) → DictElimina un sitio web.
Gestión de FTP, Mail, DNS, Databases
Cada recurso tiene métodos CRUD consistentes:
FTP:
list_ftp_users,create_ftp_user,update_ftp_user,delete_ftp_user.Mail Domains:
list_mail_domains,create_mail_domain,delete_mail_domain.Mailboxes:
list_mailboxes,create_mailbox,update_mailbox,delete_mailbox.Mail Aliases:
list_mail_aliases,create_mail_alias,delete_mail_alias.Databases:
create_database(crea usuario + DB),delete_database.DNS Zones:
list_dns_zones,create_dns_zone,delete_dns_zone.DNS Records:
list_dns_records,create_dns_record,update_dns_record,delete_dns_record. Usa métodos específicos por tipo (dns_a_add,dns_mx_add, etc.).Shell Users:
list_shell_users,create_shell_user,delete_shell_user.SSL:
request_letsencrypt_certificate(site_id, client_id).
InvoiceService
Ubicación: billing/services.py
Servicio para generación y gestión de facturas.
generate_invoice_for_tenant(tenant, period_start, period_end, ...) → InvoiceGenera una factura completa para un tenant:
Crea la factura con número secuencial (
INV-{año}-{seq:04d}).Snapshot de datos fiscales del
BillingProfile(inmutabilidad).Agrega líneas por cada suscripción activa con su precio efectivo.
Agrega líneas por registros de uso no facturados del periodo.
Marca los
UsageRecordcomoinvoiced=True.Recalcula subtotal, IVA (21% default) y total.
Ejecuta todo en una transacción atómica (
@transaction.atomic).mark_invoice_paid(invoice, gateway, gateway_payment_id, amount) → PaymentMarca una factura como pagada: crea un registro
Paymentcon estadocompletedy actualiza el estado de la factura apaid._next_invoice_number() → strGenera el siguiente número de factura secuencial por año.
_format_address(profile) → strFormatea la dirección fiscal del perfil de facturación.
UsageTracker
Ubicación: billing/services.py
Servicio para registrar el consumo de recursos facturables.
record_vm_usage(subscription, vm_name, vm_id, hours=1) → UsageRecordRegistra uso horario de una máquina virtual. Calcula el total como
hours × effective_pricede la suscripción.record_hosting_usage(subscription, site_name, site_id, days=1) → UsageRecordRegistra uso diario de hosting. Calcula el precio diario como
effective_price / 30y el total comodays × daily_price.
Estos métodos son invocados por las tareas periódicas de Celery:
billing.track_vm_usage_hourly: Cada hora.billing.track_hosting_usage_daily: Cada 24 horas.
TicketService
Ubicación: ticketing/services.py
Servicio principal del sistema de helpdesk.
create_ticket(subject, body, requester, ...) → TicketCrea un ticket nuevo con los siguientes pasos:
Instancia el
Ticketcon todos los datos proporcionados.Busca y aplica la política SLA correspondiente a la prioridad.
Calcula las fechas límite:
sla_first_response_dueysla_resolution_due.Guarda el ticket y crea el primer
TicketMessage.
add_reply(ticket, body, author, is_agent, message_type) → TicketMessageAñade una respuesta al ticket:
Si es de agente y no hay primera respuesta registrada, marca
first_responded_at.Si es de agente, cambia el estado a
pending(esperando cliente).Si es del cliente y el estado era
pending, cambia aopen.
assign_ticket(ticket, agent)Asigna un ticket a un agente. Si el ticket estaba en estado
new, lo cambia aopen.resolve_ticket(ticket)Marca el ticket como
resolvedy registraresolved_at.close_ticket(ticket)Cierra el ticket definitivamente y registra
closed_at.
EmailSender
Ubicación: ticketing/services.py
Servicio para enviar respuestas de tickets por email.
send_reply(ticket, message, mail_account=None)Envía una respuesta de ticket al email del solicitante:
Obtiene la cuenta de email activa (
MailAccount).Construye el email con headers
In-Reply-ToyReferencespara mantener el threading de email.Según el protocolo de la cuenta:
IMAP/POP3: Usa
smtplibdirectamente con STARTTLS y credenciales descifradas condecrypt_value().OAuth2 Microsoft: Delega a
MicrosoftOAuth.send_email().OAuth2 Google: Delega a
GoogleOAuth.send_email().
Soporta cuerpo en texto plano y HTML (
MIMEMultipart('alternative')).
Tareas Periódicas Celery
Las siguientes tareas se ejecutan automáticamente según el schedule definido
en settings.py (CELERY_BEAT_SCHEDULE):
Tarea |
Frecuencia |
Descripción |
|---|---|---|
|
Cada hora |
Registra uso horario de VMs activas usando |
|
Cada 24h |
Registra uso diario de sitios de hosting activos. |
|
Diaria |
Verifica si es fin de mes y genera facturas para todos los tenants con suscripciones activas. |
|
Cada hora |
Marca facturas vencidas como |
|
Cada 5 min |
Consulta las cuentas de email activas vía IMAP/OAuth2 y crea tickets automáticamente con los emails recibidos. |
|
Cada 15 min |
Verifica tickets abiertos contra sus plazos SLA y genera alertas. |
|
Diaria |
Cierra automáticamente tickets en estado |
Mixins Reutilizables
Ubicación: hosting/mixins.py
Mixins que eliminan duplicación de código en ViewSets y vistas:
is_admin_user(user) → boolCentraliza la verificación de permisos admin (superuser o rol admin).
get_tenant_or_403(request) → HostingTenantObtiene el tenant activo de la petición o lanza
PermissionDenied.TenantQuerySetMixinMixin para ViewSets que filtra querysets por tenant automáticamente. Los admins ven todos los registros; los usuarios regulares solo los de su tenant.
TenantCreateMixinAuto-asigna el tenant del usuario en operaciones
perform_create().SoftDeleteMixinImplementa borrado suave (
is_active=False) con validación de pertenencia al tenant y logging.