Files
hostpanel/ssh_manager/models.py
ilkeral f4ee7a2d0b yeni
2025-08-08 07:24:25 +03:00

388 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.db import models
import logging
from .ssh_client import SSHManager
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
from .system_settings import SystemSettings
logger = logging.getLogger(__name__)
def validate_domain(value):
"""Domain formatını kontrol et"""
if ' ' in value: # Boşluk kontrolü
raise ValidationError('Domain adında boşluk olamaz')
if not any(char.isalpha() for char in value): # En az bir harf kontrolü
raise ValidationError('Domain adında en az bir harf olmalıdır')
if not all(c.isalnum() or c in '-.' for c in value): # Geçerli karakter kontrolü
raise ValidationError('Domain adında sadece harf, rakam, tire (-) ve nokta (.) olabilir')
class Customer(models.Model):
CUSTOMER_TYPES = (
('individual', 'Bireysel'),
('corporate', 'Kurumsal'),
)
customer_type = models.CharField(max_length=20, choices=CUSTOMER_TYPES, verbose_name='Müşteri Tipi')
# Ortak alanlar
name = models.CharField(max_length=200, verbose_name='Ad/Firma Adı')
email = models.EmailField(verbose_name='E-posta')
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name='Telefon')
address = models.TextField(blank=True, null=True, verbose_name='Adres')
# Bireysel müşteri alanları
surname = models.CharField(max_length=100, blank=True, null=True, verbose_name='Soyad')
birth_date = models.DateField(blank=True, null=True, verbose_name='Doğum Tarihi')
tc_number = models.CharField(max_length=11, blank=True, null=True, verbose_name='TC Kimlik No')
# Kurumsal müşteri alanları
company_name = models.CharField(max_length=200, blank=True, null=True, verbose_name='Şirket Adı')
tax_number = models.CharField(max_length=20, blank=True, null=True, verbose_name='Vergi No')
tax_office = models.CharField(max_length=100, blank=True, null=True, verbose_name='Vergi Dairesi')
authorized_person = models.CharField(max_length=200, blank=True, null=True, verbose_name='Yetkili Kişi')
# Genel bilgiler
notes = models.TextField(blank=True, null=True, verbose_name='Notlar')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Oluşturma Tarihi')
updated_at = models.DateTimeField(auto_now=True, verbose_name='Güncelleme Tarihi')
is_active = models.BooleanField(default=True, verbose_name='Aktif')
def __str__(self):
if self.customer_type == 'corporate':
return f"{self.company_name or self.name} (Kurumsal)"
else:
return f"{self.name} {self.surname or ''} (Bireysel)".strip()
def get_display_name(self):
"""Görüntüleme için uygun isim döndür"""
if self.customer_type == 'corporate':
return self.company_name or self.name
else:
return f"{self.name} {self.surname or ''}".strip()
def clean(self):
"""Model validasyonu"""
if self.customer_type == 'individual':
if not self.surname:
raise ValidationError('Bireysel müşteriler için soyad zorunludur.')
elif self.customer_type == 'corporate':
if not self.company_name:
raise ValidationError('Kurumsal müşteriler için şirket adı zorunludur.')
class Meta:
verbose_name = "Müşteri"
verbose_name_plural = "Müşteriler"
ordering = ['-created_at']
class SSHCredential(models.Model):
name = models.CharField(max_length=100, verbose_name='Host Adı', default='Varsayılan Host')
hostname = models.CharField(max_length=255)
username = models.CharField(max_length=100)
password = models.CharField(max_length=100)
port = models.IntegerField(default=22)
base_path = models.CharField(max_length=500, help_text="Projelerin oluşturulacağı ana dizin") # Yeni alan
is_default = models.BooleanField(default=False, verbose_name='Varsayılan Host')
connection_status = models.CharField(max_length=20, default='unknown', choices=[
('connected', 'Bağlı'),
('failed', 'Başarısız'),
('unknown', 'Bilinmiyor')
])
is_online = models.BooleanField(default=False) # Bağlantı durumu
last_check = models.DateTimeField(auto_now=True) # Son kontrol zamanı
last_checked = models.DateTimeField(null=True, blank=True, verbose_name='Son Kontrol')
disk_usage = models.FloatField(null=True, blank=True, verbose_name='Disk Kullanımı (%)')
created_at = models.DateTimeField(auto_now_add=True)
def get_manager(self):
"""SSHManager instance'ı döndür"""
return SSHManager(self)
def __str__(self):
return f"{self.username}@{self.hostname}"
class Project(models.Model):
name = models.CharField(max_length=100, verbose_name='Proje Adı')
folder_name = models.CharField(max_length=100, verbose_name='Klasör Adı')
ssh_credential = models.ForeignKey(SSHCredential, on_delete=models.CASCADE)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name='Müşteri', null=True, blank=True)
url = models.TextField(null=True, blank=True, verbose_name='Site URL')
disk_usage = models.CharField(max_length=20, null=True, blank=True)
last_backup = models.DateTimeField(null=True, blank=True, verbose_name='Son Yedekleme')
host_renewal_date = models.DateField(null=True, blank=True, verbose_name='Host Yenileme Tarihi')
meta_key = models.CharField(max_length=32, null=True, blank=True, verbose_name='Meta Key', help_text='Site aktiflik kontrolü için benzersiz anahtar')
is_site_active = models.BooleanField(default=False, verbose_name='Site Aktif')
last_site_check = models.DateTimeField(null=True, blank=True, verbose_name='Son Site Kontrolü')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def generate_meta_key(self):
"""Benzersiz meta key oluştur"""
import uuid
self.meta_key = uuid.uuid4().hex[:32]
return self.meta_key
def get_meta_tag(self):
"""HTML meta tag'ı döndür"""
if not self.meta_key:
self.generate_meta_key()
self.save()
# Farklı meta tag formatları
meta_tags = [
f'<meta name="site-verification" content="{self.meta_key}">',
f'<meta name="site-verify" content="{self.meta_key}">',
f'<meta name="verification" content="{self.meta_key}">'
]
# HTML yorum içinde meta key
comment_tag = f'<!-- site-verification: {self.meta_key} -->'
# Tüm formatları birleştir
return meta_tags[0], meta_tags, comment_tag
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
def get_full_path(self):
"""Projenin tam yolunu döndür"""
return f"{self.ssh_credential.base_path}/{self.folder_name}"
@property
def has_requirements(self):
"""Projenin req.txt dosyası var mı kontrol et"""
try:
ssh_manager = self.ssh_credential.get_manager()
check_cmd = f'test -f "{self.get_full_path()}/req.txt" && echo "exists"'
stdout, stderr, status = ssh_manager.execute_command(check_cmd)
# Debug için log ekle
logger.info(f"has_requirements check for project {self.id}")
logger.info(f"Command: {check_cmd}")
logger.info(f"Status: {status}, Stdout: {stdout}, Stderr: {stderr}")
return status and stdout.strip() == "exists"
except Exception as e:
logger.exception(f"Error checking requirements for project {self.id}")
return False
finally:
if 'ssh_manager' in locals():
ssh_manager.close()
def __str__(self):
return self.name
class Meta:
verbose_name = "Proje"
verbose_name_plural = "Projeler"
ordering = ['-created_at']
class Invoice(models.Model):
INVOICE_STATUS = (
('draft', 'Taslak'),
('sent', 'Gönderildi'),
('paid', 'Ödendi'),
('overdue', 'Gecikti'),
('cancelled', 'İptal Edildi'),
)
PAYMENT_METHODS = (
('bank_transfer', 'Banka Havalesi'),
('credit_card', 'Kredi Kartı'),
('cash', 'Nakit'),
('other', 'Diğer'),
)
INVOICE_TYPES = (
('income', 'Gelir'),
('expense', 'Gider'),
)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='invoices', verbose_name='Müşteri')
invoice_number = models.CharField(max_length=50, unique=True, verbose_name='Fatura No')
invoice_type = models.CharField(max_length=10, choices=INVOICE_TYPES, default='income', verbose_name='Fatura Tipi')
issue_date = models.DateField(verbose_name='Düzenleme Tarihi')
due_date = models.DateField(verbose_name='Son Ödeme Tarihi')
status = models.CharField(max_length=20, choices=INVOICE_STATUS, default='draft', verbose_name='Durum')
payment_method = models.CharField(max_length=20, choices=PAYMENT_METHODS, default='bank_transfer', verbose_name='Ödeme Yöntemi')
total_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='Toplam Tutar')
notes = models.TextField(blank=True, null=True, verbose_name='Notlar')
payment_notes = models.TextField(blank=True, null=True, verbose_name='Ödeme Notları')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Oluşturma Tarihi')
updated_at = models.DateTimeField(auto_now=True, verbose_name='Güncelleme Tarihi')
def __str__(self):
return f"Fatura #{self.invoice_number} - {self.customer.get_display_name()}"
def save(self, *args, **kwargs):
if not self.invoice_number:
# Otomatik fatura numarası oluştur (F-YIL-AYGUN-XXXX)
from datetime import datetime
year = datetime.now().strftime('%Y')
day_month = datetime.now().strftime('%d%m')
# Son fatura numarasını bul
last_invoice = Invoice.objects.filter(
invoice_number__startswith=f'F-{year}'
).order_by('-invoice_number').first()
if last_invoice:
try:
last_num = int(last_invoice.invoice_number.split('-')[-1])
new_num = last_num + 1
except (ValueError, IndexError):
new_num = 1
else:
new_num = 1
self.invoice_number = f'F-{year}-{day_month}-{new_num:04d}'
super().save(*args, **kwargs)
class Meta:
verbose_name = "Fatura"
verbose_name_plural = "Faturalar"
ordering = ['-issue_date', '-id']
class InvoiceItem(models.Model):
invoice = models.ForeignKey(Invoice, related_name='items', on_delete=models.CASCADE, verbose_name='Fatura')
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='İlişkili Proje')
description = models.CharField(max_length=255, verbose_name='ıklama')
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Tutar')
def __str__(self):
return f"{self.description} - {self.invoice.invoice_number}"
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.update_invoice_total()
def delete(self, *args, **kwargs):
invoice = self.invoice
super().delete(*args, **kwargs)
# Fatura silindiğinde tutarı güncelle
self.update_invoice_total()
def update_invoice_total(self):
"""Fatura toplam tutarını güncelle"""
from decimal import Decimal, InvalidOperation
invoice = self.invoice
items = invoice.items.all()
# Toplam tutar - güvenli dönüşümle hesapla
total = Decimal('0.00')
for item in items:
try:
# Eğer item.amount None ise veya geçersizse, 0 olarak kabul et
if item.amount is None:
continue
# String ise decimal'e çevir
if isinstance(item.amount, str):
if ',' in item.amount:
item.amount = item.amount.replace(',', '.')
item_amount = Decimal(item.amount)
else:
item_amount = Decimal(str(item.amount))
total += item_amount
except (InvalidOperation, ValueError, TypeError) as e:
print(f"Invoice total hesaplama hatası: {str(e)}")
# Hatalı öğeyi atla
continue
# Fatura tutarını güncelle
invoice.total_amount = total
invoice.save()
class Meta:
verbose_name = "Fatura Kalemi"
verbose_name_plural = "Fatura Kalemleri"
ordering = ['id']
class SSHLog(models.Model):
LOG_TYPES = (
('connection', 'Bağlantı Kontrolü'),
('command', 'Komut Çalıştırma'),
('folder', 'Klasör İşlemi'),
('file', 'Dosya İşlemi'),
('backup', 'Yedekleme'), # Yeni tip ekleyelim
)
ssh_credential = models.ForeignKey(SSHCredential, on_delete=models.CASCADE)
log_type = models.CharField(max_length=20, choices=LOG_TYPES)
command = models.TextField()
output = models.TextField()
status = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.ssh_credential.hostname} - {self.log_type} - {self.created_at}"
class Meta:
ordering = ['-created_at']
class Backup(models.Model):
BACKUP_STATUS = (
('running', 'Devam Ediyor'),
('completed', 'Tamamlandı'),
('failed', 'Başarısız'),
('cancelled', 'İptal Edildi'),
)
BACKUP_TYPE = (
('manual', 'Manuel'),
('auto', 'Otomatik'),
('scheduled', 'Zamanlanmış'),
)
project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='Proje')
backup_type = models.CharField(max_length=20, choices=BACKUP_TYPE, default='manual', verbose_name='Yedekleme Tipi')
status = models.CharField(max_length=20, choices=BACKUP_STATUS, default='running', verbose_name='Durum')
start_time = models.DateTimeField(auto_now_add=True, verbose_name='Başlangıç Zamanı')
end_time = models.DateTimeField(null=True, blank=True, verbose_name='Bitiş Zamanı')
file_path = models.CharField(max_length=500, null=True, blank=True, verbose_name='Dosya Yolu')
file_size = models.BigIntegerField(null=True, blank=True, verbose_name='Dosya Boyutu') # BigIntegerField kullanarak büyük dosya boyutlarını destekle
error_message = models.TextField(null=True, blank=True, verbose_name='Hata Mesajı')
notes = models.TextField(null=True, blank=True, verbose_name='Notlar')
def __str__(self):
return f"{self.project.name} - {self.start_time.strftime('%d.%m.%Y %H:%M')}"
@property
def duration(self):
"""Yedekleme süresini hesapla"""
if self.end_time:
duration = self.end_time - self.start_time
total_seconds = int(duration.total_seconds())
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
if hours > 0:
return f"{hours}s {minutes}d {seconds}s"
elif minutes > 0:
return f"{minutes}d {seconds}s"
else:
return f"{seconds}s"
return "-"
@property
def status_badge_class(self):
"""Durum badge CSS sınıfı"""
status_classes = {
'running': 'bg-info',
'completed': 'bg-success',
'failed': 'bg-danger',
'cancelled': 'bg-warning'
}
return status_classes.get(self.status, 'bg-secondary')
class Meta:
verbose_name = "Yedekleme"
verbose_name_plural = "Yedeklemeler"
ordering = ['-start_time']