\x89PNG\r\n\x1a\n\x00\x00\x00\x0DIHDR\x00\x00\x00\x01\x00 \x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00 \x0AIDATx\x9Ccb\x00\x00\x00\x06\x00\x03\x1A\x05\x9D\x00\x00 \x00\x00IEND\xAE\x42\x60\x82 csarite.com
KUJUNTI.ID MINISH3LL
Path : /var/www/html/yedekledb24/DatabaseBackupMaster/
(S)h3ll Cr3at0r :
F!le Upl0ad :

B-Con CMD Config cPanel C-Rdp D-Log Info Jump Mass Ransom Symlink vHost Zone-H

Current File : /var/www/html/yedekledb24/DatabaseBackupMaster/routes.py


import os
import datetime
import psycopg2
from flask import render_template, request, redirect, url_for, flash, jsonify, session
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.sql.expression import text
from flask_babel import _, get_locale
from flask_login import login_user, logout_user, current_user, login_required
from app import app, db, logger, mail
from models import DatabaseConnection, StorageTarget, BackupJob, BackupLog, SmtpSettings, User
from forms import (DatabaseConnectionForm, StorageTargetForm, BackupJobForm, SmtpSettingsForm, 
                  LoginForm, RegistrationForm, EncryptionKeyForm)
from backup_utils import test_database_connection, run_backup
from storage_utils import test_storage_connection, get_storage_space
from scheduler import schedule_backup_job, remove_scheduled_job
from restore_database import restore_from_backup
from database_performance import get_top_queries as pg_get_top_queries, get_database_stats as pg_get_database_stats
from database_performance import get_table_stats as pg_get_table_stats, enable_pg_stat_statements, pg_stat_statements_enabled
# MySQL performans fonksiyonlarını import et
try:
    from mysql_performance import get_top_queries as mysql_get_top_queries, get_database_stats as mysql_get_database_stats
    from mysql_performance import get_table_stats as mysql_get_table_stats, enable_performance_schema, performance_schema_enabled
    MYSQL_PERFORMANCE_AVAILABLE = True
except ImportError:
    MYSQL_PERFORMANCE_AVAILABLE = False

@app.route('/')
@login_required
def index():
    """Render the dashboard page with backup statistics."""
    # Get recent backup logs
    recent_logs = BackupLog.query.order_by(BackupLog.timestamp.desc()).limit(10).all()
    
    # Get database connections
    db_connections = DatabaseConnection.query.all()
    
    # Get storage targets
    storage_targets = StorageTarget.query.all()
    
    # Get backup jobs
    backup_jobs = BackupJob.query.all()
    
    # Count success and failed backups
    success_count = BackupLog.query.filter_by(status='success').count()
    failed_count = BackupLog.query.filter_by(status='failed').count()
    
    # Get storage space info
    storage_info = []
    for target in storage_targets:
        try:
            free_space, total_space = get_storage_space(target)
            percent_free = (free_space / total_space) * 100 if total_space > 0 else 0
            warning = percent_free < 10  # Warning if less than 10% space available
            
            storage_info.append({
                'id': target.id,
                'name': target.name,
                'free_space': free_space,
                'total_space': total_space,
                'percent_free': percent_free,
                'warning': warning
            })
        except Exception as e:
            logger.error("Error getting storage space for {}: {}".format(target.name, str(e)))
            storage_info.append({
                'id': target.id,
                'name': target.name,
                'free_space': 0,
                'total_space': 0,
                'percent_free': 0,
                'warning': True,
                'error': str(e)
            })
    
    return render_template('dashboard.html', 
                          recent_logs=recent_logs,
                          db_count=len(db_connections),
                          storage_count=len(storage_targets),
                          job_count=len(backup_jobs),
                          success_count=success_count,
                          failed_count=failed_count,
                          storage_info=storage_info)

@app.route('/databases')
@login_required
def databases():
    """Render the database connections page."""
    db_connections = DatabaseConnection.query.all()
    form = DatabaseConnectionForm()
    return render_template('databases.html', db_connections=db_connections, form=form)

@app.route('/databases/add', methods=['POST'])
@login_required
def add_database():
    """Add a new database connection."""
    # Check if user has permission to manage database connections
    if not current_user.can_manage_db_connections():
        flash(_('Veritabanı bağlantısı ekleme izniniz yok.'), 'danger')
        return redirect(url_for('databases'))
    
    form = DatabaseConnectionForm()
    if form.validate_on_submit():
        try:
            # Test connection before saving
            connection_success = test_database_connection(
                db_type=form.db_type.data,
                host=form.host.data,
                port=form.port.data,
                username=form.username.data,
                password=form.password.data,
                database=form.database.data
            )
            
            if connection_success:
                new_db = DatabaseConnection(
                    name=form.name.data,
                    db_type=form.db_type.data,
                    host=form.host.data,
                    port=form.port.data,
                    username=form.username.data,
                    password=form.password.data,
                    database=form.database.data,
                    pg_version=form.pg_version.data if form.db_type.data == 'postgresql' else None
                )
                
                db.session.add(new_db)
                db.session.commit()
                flash(_('Veritabanı bağlantısı başarıyla eklendi!'), 'success')
            else:
                flash(_('Veritabanına bağlanılamadı. Lütfen bilgilerinizi kontrol edin.'), 'danger')
        except SQLAlchemyError as e:
            db.session.rollback()
            flash(f'Database error: {str(e)}', 'danger')
        except Exception as e:
            flash(f'Error: {str(e)}', 'danger')
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f'{getattr(form, field).label.text}: {error}', 'danger')
    
    return redirect(url_for('databases'))

@app.route('/databases/delete/<int:db_id>', methods=['POST'])
@login_required
def delete_database(db_id):
    """Delete a database connection."""
    # Check if user has permission to manage database connections
    if not current_user.can_manage_db_connections():
        flash(_('Veritabanı bağlantısı silme izniniz yok.'), 'danger')
        return redirect(url_for('databases'))
        
    try:
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        # Check if database is used in any backup job
        jobs = BackupJob.query.filter_by(database_id=db_id).all()
        if jobs:
            flash(_('Yedekleme görevlerinde kullanılan veritabanı silinemez.'), 'danger')
            return redirect(url_for('databases'))
        
        db.session.delete(db_conn)
        db.session.commit()
        flash(_('Veritabanı bağlantısı başarıyla silindi!'), 'success')
    except SQLAlchemyError as e:
        db.session.rollback()
        flash(f'Veritabanı hatası: {str(e)}', 'danger')
    except Exception as e:
        flash(f'Hata: {str(e)}', 'danger')
    
    return redirect(url_for('databases'))

@app.route('/databases/test/<int:db_id>')
@login_required
def test_database(db_id):
    """Test a database connection."""
    # Test operations are allowed for all authenticated users
    # since this is just reading data, not modifying
    
    try:
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        connection_success = test_database_connection(
            db_type=db_conn.db_type,
            host=db_conn.host,
            port=db_conn.port,
            username=db_conn.username,
            password=db_conn.password,
            database=db_conn.database
        )
        
        if connection_success:
            flash(_('Veritabanı bağlantısı başarılı!'), 'success')
        else:
            flash(_('Veritabanına bağlanılamadı.'), 'danger')
    except Exception as e:
        flash(f'Veritabanı bağlantı testi hatası: {str(e)}', 'danger')
    
    return redirect(url_for('databases'))

@app.route('/storage')
@login_required
def storage():
    """Render the storage targets page."""
    storage_targets = StorageTarget.query.all()
    form = StorageTargetForm()
    
    # Get storage space info
    for target in storage_targets:
        try:
            free_space, total_space = get_storage_space(target)
            target.free_space = free_space
            target.total_space = total_space
            target.percent_free = (free_space / total_space) * 100 if total_space > 0 else 0
            target.warning = target.percent_free < 10
        except Exception as e:
            logger.error("Error getting storage space for {}: {}".format(target.name, str(e)))
            target.free_space = 0
            target.total_space = 0
            target.percent_free = 0
            target.warning = True
            target.error = str(e)
    
    return render_template('storage.html', storage_targets=storage_targets, form=form)

@app.route('/storage/add', methods=['POST'])
@login_required
def add_storage():
    """Add a new storage target."""
    # Check if user has permission to manage storage
    if not current_user.can_manage_storage():
        flash(_('Depolama hedefi ekleme izniniz yok.'), 'danger')
        return redirect(url_for('storage'))
        
    form = StorageTargetForm()
    if form.validate_on_submit():
        try:
            # Test connection before saving
            connection_success = test_storage_connection(
                storage_type=form.storage_type.data,
                host=form.host.data,
                port=form.port.data,
                username=form.username.data,
                password=form.password.data,
                path=form.path.data
            )
            
            if connection_success:
                new_storage = StorageTarget(
                    name=form.name.data,
                    storage_type=form.storage_type.data,
                    host=form.host.data,
                    port=form.port.data,
                    username=form.username.data,
                    password=form.password.data,
                    path=form.path.data
                )
                
                db.session.add(new_storage)
                db.session.commit()
                flash(_('Depolama hedefi başarıyla eklendi!'), 'success')
            else:
                flash(_('Depolama hedefine bağlanılamadı. Lütfen kimlik bilgilerinizi kontrol edin.'), 'danger')
        except SQLAlchemyError as e:
            db.session.rollback()
            flash(f'Database error: {str(e)}', 'danger')
        except Exception as e:
            flash(f'Error: {str(e)}', 'danger')
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f'{getattr(form, field).label.text}: {error}', 'danger')
    
    return redirect(url_for('storage'))

@app.route('/storage/delete/<int:storage_id>', methods=['POST'])
@login_required
def delete_storage(storage_id):
    """Delete a storage target."""
    # Check if user has permission to manage storage
    if not current_user.can_manage_storage():
        flash(_('Depolama hedefi silme izniniz yok.'), 'danger')
        return redirect(url_for('storage'))
        
    try:
        storage_target = StorageTarget.query.get_or_404(storage_id)
        
        # Check if storage is used in any backup job
        jobs = BackupJob.query.filter_by(storage_id=storage_id).all()
        if jobs:
            flash(_('Yedekleme görevlerinde kullanılan depolama hedefi silinemez.'), 'danger')
            return redirect(url_for('storage'))
        
        db.session.delete(storage_target)
        db.session.commit()
        flash(_('Depolama hedefi başarıyla silindi!'), 'success')
    except SQLAlchemyError as e:
        db.session.rollback()
        flash(f'Veritabanı hatası: {str(e)}', 'danger')
    except Exception as e:
        flash(f'Hata: {str(e)}', 'danger')
    
    return redirect(url_for('storage'))

@app.route('/storage/test/<int:storage_id>')
@login_required
def test_storage(storage_id):
    """Test a storage connection."""
    # Test operations are allowed for all authenticated users
    # since this is just reading data, not modifying
    
    try:
        storage_target = StorageTarget.query.get_or_404(storage_id)
        
        connection_success = test_storage_connection(
            storage_type=storage_target.storage_type,
            host=storage_target.host,
            port=storage_target.port,
            username=storage_target.username,
            password=storage_target.password,
            path=storage_target.path
        )
        
        if connection_success:
            flash(_('Depolama hedefine bağlantı başarılı!'), 'success')
        else:
            flash(_('Depolama hedefine bağlanılamadı.'), 'danger')
    except Exception as e:
        flash(f'Depolama hedefi bağlantı testi hatası: {str(e)}', 'danger')
    
    return redirect(url_for('storage'))

@app.route('/backup_jobs')
@login_required
def backup_jobs():
    """Render the backup jobs page."""
    backup_jobs = BackupJob.query.all()
    db_connections = DatabaseConnection.query.all()
    storage_targets = StorageTarget.query.all()
    form = BackupJobForm()
    
    # Create a simple CSRF form for delete operations
    from flask_wtf import FlaskForm
    csrf_form = FlaskForm()
    
    # Populate form choices
    form.database_id.choices = [(db.id, db.name) for db in db_connections]
    
    # Prepare database options with data-db-type attribute for the template
    db_options = {}
    for db in db_connections:
        db_options[db.id] = db.db_type
        
    form.storage_id.choices = [(s.id, s.name) for s in storage_targets]
    
    # Encryption key choices if the table exists
    try:
        from models import EncryptionKey
        encryption_keys = EncryptionKey.query.all()
        form.encryption_key_id.choices = [(0, 'Anahtar Seçiniz...')] + [(k.id, f"{k.name} <{k.email}>") for k in encryption_keys]
    except:
        # Encryption key table might not exist yet
        form.encryption_key_id.choices = [(0, 'Şifreleme Anahtarları Bulunamadı')]
    
    # .env dosyasından varsayılan şifreyi al
    default_passphrase = os.environ.get('BACKUP_ENCRYPTION_PSK', '')
    if default_passphrase:
        form.encryption_passphrase.data = default_passphrase
    
    # Add information to backup jobs for display
    for job in backup_jobs:
        # Get the last backup log
        job.last_log = BackupLog.query.filter_by(job_id=job.id).order_by(BackupLog.timestamp.desc()).first()
        
        # Check if the next run is scheduled
        job.next_run = None
        try:
            scheduler = app.scheduler
            
            # APScheduler 3.x için uyumlu kod - get_jobs() artık bir liste döndürüyor
            job_id = f"backup_job_{job.id}"
            for scheduler_job in scheduler.get_jobs():
                if scheduler_job.id == job_id and hasattr(scheduler_job, 'next_run_time'):
                    job.next_run = scheduler_job.next_run_time
                    break
        except (AttributeError, Exception) as e:
            # Scheduler erişilemiyorsa hata logla ama işleme devam et
            logger.error("Zamanlayıcıya erişilirken hata oluştu: {}".format(str(e)))
            # Hata durumunda job.next_run zaten None olarak kalır
    
    return render_template('backup_jobs.html', 
                          backup_jobs=backup_jobs, 
                          db_connections=db_connections,
                          storage_targets=storage_targets,
                          form=form,
                          csrf_form=csrf_form)

@app.route('/backup_jobs/add', methods=['POST'])
@login_required
def add_backup_job():
    """Add a new backup job."""
    # Check if user has permission to manage backup jobs
    if not current_user.can_manage_backup_jobs():
        flash(_('Yedekleme görevi ekleme izniniz yok.'), 'danger')
        return redirect(url_for('backup_jobs'))
        
    db_connections = DatabaseConnection.query.all()
    storage_targets = StorageTarget.query.all()
    
    form = BackupJobForm()
    form.database_id.choices = [(db.id, db.name) for db in db_connections]
    form.storage_id.choices = [(s.id, s.name) for s in storage_targets]
    
    # Encryption key choices if the table exists
    try:
        from models import EncryptionKey
        encryption_keys = EncryptionKey.query.all()
        form.encryption_key_id.choices = [(0, 'Anahtar Seçiniz...')] + [(k.id, f"{k.name} <{k.email}>") for k in encryption_keys]
    except:
        # Encryption key table might not exist yet
        form.encryption_key_id.choices = [(0, 'Şifreleme Anahtarları Bulunamadı')]
    
    if form.validate_on_submit():
        try:
            # Base job properties
            new_job = BackupJob(
                name=form.name.data,
                database_id=form.database_id.data,
                storage_id=form.storage_id.data,
                schedule_type=form.schedule_type.data,
                schedule_value=form.schedule_value.data,
                retention_days=form.retention_days.data,
                enabled=form.enabled.data
            )
            
            # Add encryption properties if supported
            try:
                # If encryption_type field exists in the model, set it
                if hasattr(new_job, 'encrypt_backup'):
                    new_job.encrypt_backup = form.encrypt_backup.data
                    new_job.encryption_type = form.encryption_type.data if form.encrypt_backup.data else None
                    
                    # Set encryption key or passphrase based on encryption type
                    if form.encrypt_backup.data:
                        if form.encryption_type.data == 'asymmetric' and form.encryption_key_id.data > 0:
                            new_job.encryption_key_id = form.encryption_key_id.data
                        elif form.encryption_type.data == 'symmetric' and form.encryption_passphrase.data:
                            new_job.encryption_passphrase = form.encryption_passphrase.data
            except:
                # The encryption fields might not exist in the model yet
                pass
                
            # Add compression properties if supported
            try:
                # If compress_backup field exists in the model, set it
                if hasattr(new_job, 'compress_backup'):
                    new_job.compress_backup = form.compress_backup.data
                    new_job.compression_type = form.compression_type.data if form.compress_backup.data else 'zip'
            except:
                # The compression fields might not exist in the model yet
                pass
            
            db.session.add(new_job)
            db.session.commit()
            
            # Schedule the job if enabled
            if new_job.enabled:
                schedule_backup_job(new_job)
                
            flash(_('Yedekleme görevi başarıyla eklendi!'), 'success')
        except SQLAlchemyError as e:
            db.session.rollback()
            flash(f'Veritabanı hatası: {str(e)}', 'danger')
        except Exception as e:
            flash(f'Hata: {str(e)}', 'danger')
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f'{getattr(form, field).label.text}: {error}', 'danger')
    
    return redirect(url_for('backup_jobs'))
    
@app.route('/backup_jobs/edit/<int:job_id>', methods=['GET', 'POST'])
@login_required
def edit_backup_job(job_id):
    """Edit an existing backup job."""
    # Check if user has permission to manage backup jobs
    if not current_user.can_manage_backup_jobs():
        flash(_('Yedekleme görevi düzenleme izniniz yok.'), 'danger')
        return redirect(url_for('backup_jobs'))
        
    # Get the job to edit
    job = BackupJob.query.get_or_404(job_id)
    
    # Get database connections and storage targets
    db_connections = DatabaseConnection.query.all()
    storage_targets = StorageTarget.query.all()
    
    # Create the form and populate with current job data
    form = BackupJobForm(obj=job)
    
    # Set form choices - database_id için data-db-type özniteliğini ekle
    form.database_id.choices = [(db.id, db.name) for db in db_connections]
    
    # Form choices özelliğine erişilemediği için, şablonda kullanmak için db_connections listesini kullanıyoruz
    # Bu şekilde data-db-type özelliğini ekleyebiliriz
    
    form.storage_id.choices = [(s.id, s.name) for s in storage_targets]
    
    # Encryption key choices if the table exists
    try:
        from models import EncryptionKey
        encryption_keys = EncryptionKey.query.all()
        form.encryption_key_id.choices = [(0, 'Anahtar Seçiniz...')] + [(k.id, f"{k.name} <{k.email}>") for k in encryption_keys]
    except:
        # Encryption key table might not exist yet
        form.encryption_key_id.choices = [(0, 'Şifreleme Anahtarları Bulunamadı')]
    
    # Handle form submission
    if request.method == 'POST' and form.validate_on_submit():
        try:
            # Update basic job properties
            job.name = form.name.data
            job.database_id = form.database_id.data
            job.storage_id = form.storage_id.data
            job.schedule_type = form.schedule_type.data
            job.schedule_value = form.schedule_value.data
            job.retention_days = form.retention_days.data
            
            # Check if job enabled status changed
            was_enabled = job.enabled
            job.enabled = form.enabled.data
            
            # Update encryption properties if supported
            try:
                if hasattr(job, 'encrypt_backup'):
                    job.encrypt_backup = form.encrypt_backup.data
                    job.encryption_type = form.encryption_type.data if form.encrypt_backup.data else None
                    
                    # Update encryption key or passphrase based on encryption type
                    if form.encrypt_backup.data:
                        if form.encryption_type.data == 'asymmetric' and form.encryption_key_id.data > 0:
                            job.encryption_key_id = form.encryption_key_id.data
                        elif form.encryption_type.data == 'symmetric' and form.encryption_passphrase.data:
                            job.encryption_passphrase = form.encryption_passphrase.data
            except:
                # The encryption fields might not exist in the model yet
                pass
                
            # Update compression properties if supported
            try:
                if hasattr(job, 'compress_backup'):
                    job.compress_backup = form.compress_backup.data
                    job.compression_type = form.compression_type.data if form.compress_backup.data else 'zip'
            except:
                # The compression fields might not exist in the model yet
                pass
                
            # Update backup method properties if supported
            try:
                if hasattr(job, 'backup_method'):
                    job.backup_method = form.backup_method.data
                    
                    # XtraBackup options
                    if hasattr(job, 'xtrabackup_options') and form.xtrabackup_options.data:
                        job.xtrabackup_options = form.xtrabackup_options.data
                    elif hasattr(job, 'xtrabackup_options'):
                        job.xtrabackup_options = None
            except Exception as e:
                # The backup method fields might not exist in the model yet
                logger.error(f"Backup method update error: {str(e)}")
                pass
            
            # Save changes to database
            db.session.commit()
            
            # Update scheduler if needed
            if was_enabled != job.enabled:
                if job.enabled:
                    # Add to scheduler
                    schedule_backup_job(job)
                else:
                    # Remove from scheduler
                    remove_scheduled_job(job.id)
            elif job.enabled:
                # Update existing scheduled job
                remove_scheduled_job(job.id)
                schedule_backup_job(job)
                
            flash(_('Yedekleme görevi başarıyla güncellendi!'), 'success')
            return redirect(url_for('backup_jobs'))
        except SQLAlchemyError as e:
            db.session.rollback()
            flash(f'Veritabanı hatası: {str(e)}', 'danger')
        except Exception as e:
            flash(f'Hata: {str(e)}', 'danger')
    elif request.method == 'GET':
        # Populate form with existing job data
        form.name.data = job.name
        form.database_id.data = job.database_id
        form.storage_id.data = job.storage_id
        form.schedule_type.data = job.schedule_type
        form.schedule_value.data = job.schedule_value
        form.retention_days.data = job.retention_days
        form.enabled.data = job.enabled
        
        # Set encryption form fields if they exist
        try:
            if hasattr(job, 'encrypt_backup'):
                form.encrypt_backup.data = job.encrypt_backup
                form.encryption_type.data = job.encryption_type or ''
                
                if job.encryption_key_id:
                    form.encryption_key_id.data = job.encryption_key_id
                    
                # Şifre yoksa veya boşsa .env'den al
                if job.encryption_passphrase:
                    form.encryption_passphrase.data = job.encryption_passphrase
                else:
                    default_passphrase = os.environ.get('BACKUP_ENCRYPTION_PSK', '')
                    if default_passphrase:
                        form.encryption_passphrase.data = default_passphrase
        except:
            pass
            
        # Set compression form fields if they exist
        try:
            if hasattr(job, 'compress_backup'):
                form.compress_backup.data = job.compress_backup
                form.compression_type.data = job.compression_type or 'zip'
        except:
            pass
            
        # Set backup method form fields if they exist
        try:
            if hasattr(job, 'backup_method'):
                form.backup_method.data = job.backup_method or 'full'
                
                if hasattr(job, 'xtrabackup_options') and job.xtrabackup_options:
                    form.xtrabackup_options.data = job.xtrabackup_options
        except Exception as e:
            logger.error(f"Backup method field load error: {str(e)}")
            pass
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f'{getattr(form, field).label.text}: {error}', 'danger')
    
    return render_template('edit_backup_job.html', 
                          form=form, 
                          job=job,
                          db_connections=db_connections,
                          storage_targets=storage_targets)

@app.route('/backup_jobs/delete/<int:job_id>', methods=['POST'])
@login_required
def delete_backup_job(job_id):
    """Delete a backup job."""
    # Check if user has permission to manage backup jobs
    if not current_user.can_manage_backup_jobs():
        flash(_('Yedekleme görevi silme izniniz yok.'), 'danger')
        return redirect(url_for('backup_jobs'))
        
    try:
        # Silmeden önce logları temizleme yaklaşımı
        # İlk olarak veritabanına direkt SQL sorgusu çalıştıralım
        try:
            # Log kayıtlarını doğrudan SQL ile silelim
            sql = "DELETE FROM backup_log WHERE job_id = :job_id"
            db.session.execute(text(sql), {"job_id": job_id})
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            logger.error("Log kayıtları silinirken hata: {}".format(str(e)))
        
        # Şimdi işi güvenle silebiliriz
        job = BackupJob.query.get_or_404(job_id)
        
        # Remove from scheduler if enabled
        if job.enabled:
            remove_scheduled_job(job.id)
        
        # İşi sil
        db.session.delete(job)
        db.session.commit()
        flash(_('Yedekleme görevi başarıyla silindi!'), 'success')
            
    except SQLAlchemyError as e:
        db.session.rollback()
        flash(f'Veritabanı hatası: {str(e)}', 'danger')
    except Exception as e:
        flash(f'Hata: {str(e)}', 'danger')
    
    return redirect(url_for('backup_jobs'))

@app.route('/backup_jobs/toggle/<int:job_id>', methods=['POST'])
@login_required
def toggle_backup_job(job_id):
    """Toggle enabled status for a backup job."""
    # Check if user has permission to manage backup jobs
    if not current_user.can_manage_backup_jobs():
        flash(_('Yedekleme görevi durumunu değiştirme izniniz yok.'), 'danger')
        return redirect(url_for('backup_jobs'))
        
    try:
        job = BackupJob.query.get_or_404(job_id)
        job.enabled = not job.enabled
        
        if job.enabled:
            # Add to scheduler
            schedule_backup_job(job)
            flash(_('Yedekleme görevi başarıyla etkinleştirildi!'), 'success')
        else:
            # Remove from scheduler
            remove_scheduled_job(job.id)
            flash(_('Yedekleme görevi başarıyla devre dışı bırakıldı!'), 'success')
            
        db.session.commit()
    except SQLAlchemyError as e:
        db.session.rollback()
        flash(f'Veritabanı hatası: {str(e)}', 'danger')
    except Exception as e:
        flash(f'Hata: {str(e)}', 'danger')
    
    return redirect(url_for('backup_jobs'))

@app.route('/backup_jobs/run/<int:job_id>', methods=['POST'])
@login_required
def run_backup_job(job_id):
    """Run a backup job manually."""
    # Both admin and regular users should be able to run backups
    # No explicit permission check since this is a read operation
    # But we still check for authenticated users with login_required
    
    try:
        job = BackupJob.query.get_or_404(job_id)
        
        # Run the backup with email notifications
        success = run_backup(job, send_email=True)
        
        if success:
            flash(_('Yedekleme görevi başarıyla çalıştırıldı! E-posta bildirimleri gönderildi.'), 'success')
        else:
            flash(_('Yedekleme görevi çalıştırma başarısız oldu. Detaylar için logları kontrol edin.'), 'danger')
    except Exception as e:
        flash(f'Hata: {str(e)}', 'danger')
    
    return redirect(url_for('backup_jobs'))

@app.route('/logs')
@login_required
def logs():
    """Render the backup logs page."""
    page = request.args.get('page', 1, type=int)
    per_page = 20
    
    backup_logs = BackupLog.query.order_by(BackupLog.timestamp.desc()).paginate(page=page, per_page=per_page)
    
    # Create a form for CSRF protection
    from flask_wtf import FlaskForm
    form = FlaskForm()
    
    return render_template('logs.html', backup_logs=backup_logs, form=form)

@app.route('/restore/<int:log_id>', methods=['POST'])
@login_required
def restore_backup(log_id):
    """Restore a database from a backup."""
    # Check if user has permission to manage backup jobs
    if not current_user.can_manage_backup_jobs():
        flash(_('Veritabanı geri yükleme izniniz yok.'), 'danger')
        return redirect(url_for('logs'))
        
    try:
        # Find the backup log entry
        backup_log = BackupLog.query.get_or_404(log_id)
        
        # Make sure the backup was successful
        if backup_log.status != 'success':
            flash(_('Sadece başarılı yedekler geri yüklenebilir.'), 'danger')
            return redirect(url_for('logs'))
            
        # Make sure the backup file exists
        if not backup_log.backup_file or not os.path.exists(backup_log.backup_file):
            flash(_('Yedek dosyası bulunamadı.'), 'danger')
            return redirect(url_for('logs'))
            
        # Şifreli yedekler için şifre kontrolü
        passphrase = request.form.get('passphrase', None)
        
        # CSRF koruması için form oluşturulmalı
        from flask_wtf import FlaskForm
        form = FlaskForm()
        
        # Perform the restore with passphrase if provided
        result = restore_from_backup(log_id, passphrase)
        
        # Şifre gerekiyorsa, şifre giriş formunu göster
        if not result['success'] and result.get('needs_passphrase'):
            return render_template('restore_password.html', 
                                  log_id=log_id, 
                                  job_name=backup_log.job.name, 
                                  database_name=backup_log.job.database.name,
                                  form=form)
        
        if result['success']:
            # Create a new log entry for the restore
            new_log = BackupLog(
                job_id=backup_log.job_id,
                status='success',
                message=f"Geri yükleme başarılı: {os.path.basename(backup_log.backup_file)}",
                backup_file=backup_log.backup_file,
                backup_size=backup_log.backup_size,
                duration=0  # We don't track duration for restores
            )
            db.session.add(new_log)
            db.session.commit()
            
            flash(_('Veritabanı başarıyla geri yüklendi!'), 'success')
        else:
            flash(_('Veritabanı geri yükleme hatası: ') + result['message'], 'danger')
            
    except Exception as e:
        logger.error("Restore error: {}".format(str(e)))
        flash('Geri yükleme hatası: {}'.format(str(e)), 'danger')
    
    return redirect(url_for('logs'))

@app.route('/settings')
@login_required
def settings():
    """Render the settings page."""
    # Retrieve SMTP settings from database or environment variables
    smtp_settings = SmtpSettings.get_settings()
    
    # Create SMTP settings form
    smtp_form = SmtpSettingsForm()
    
    # Create encryption key form
    encryption_form = EncryptionKeyForm()
    
    # Get encryption keys if the table exists
    encryption_keys = []
    try:
        from models import EncryptionKey
        encryption_keys = EncryptionKey.query.all()
    except:
        # Encryption table might not exist yet
        pass
    
    # If there's an existing SMTP configuration in the database, populate the form
    settings_record = SmtpSettings.query.first()
    if settings_record:
        # Default connection type based on port and SSL/TLS settings
        connection_type = 'ssl' if settings_record.mail_use_ssl else 'tls'
        
        # Populate form
        smtp_form.mail_server.data = settings_record.mail_server
        smtp_form.mail_username.data = settings_record.mail_username
        smtp_form.mail_password.data = settings_record.mail_password
        smtp_form.mail_default_sender.data = settings_record.mail_default_sender
        smtp_form.mail_connection_type.data = connection_type
        smtp_form.notification_emails.data = settings_record.notification_emails
    
    return render_template('settings.html',
                          mail_settings=smtp_settings,
                          smtp_form=smtp_form,
                          encryption_form=encryption_form,
                          encryption_keys=encryption_keys)

@app.route('/settings/smtp', methods=['POST'])
@login_required
def save_smtp_settings():
    """Save SMTP email settings."""
    # Only admin users can modify SMTP settings
    if not current_user.is_admin:
        flash(_('E-posta ayarlarını değiştirme izniniz yok.'), 'danger')
        return redirect(url_for('settings'))
        
    form = SmtpSettingsForm()
    
    if form.validate_on_submit():
        try:
            # Determine SSL/TLS based on connection type
            is_tls = form.mail_connection_type.data == 'tls'
            is_ssl = form.mail_connection_type.data == 'ssl'
            
            # Set port based on connection type if not explicitly provided
            port = 587 if is_tls else 465
            
            # Check if we have existing settings to update
            settings = SmtpSettings.query.first()
            
            if settings:
                # Update existing record
                settings.mail_server = form.mail_server.data
                settings.mail_port = port
                settings.mail_use_tls = is_tls
                settings.mail_use_ssl = is_ssl
                settings.mail_username = form.mail_username.data
                settings.mail_password = form.mail_password.data
                settings.mail_default_sender = form.mail_default_sender.data
                settings.notification_emails = form.notification_emails.data
            else:
                # Create new record
                settings = SmtpSettings(
                    mail_server=form.mail_server.data,
                    mail_port=port,
                    mail_use_tls=is_tls,
                    mail_use_ssl=is_ssl,
                    mail_username=form.mail_username.data,
                    mail_password=form.mail_password.data,
                    mail_default_sender=form.mail_default_sender.data,
                    notification_emails=form.notification_emails.data
                )
                db.session.add(settings)
            
            # Save to database
            db.session.commit()
            
            # Update mail configuration
            app.config['MAIL_SERVER'] = settings.mail_server
            app.config['MAIL_PORT'] = settings.mail_port
            app.config['MAIL_USE_TLS'] = settings.mail_use_tls
            app.config['MAIL_USE_SSL'] = settings.mail_use_ssl
            app.config['MAIL_USERNAME'] = settings.mail_username
            app.config['MAIL_PASSWORD'] = settings.mail_password
            app.config['MAIL_DEFAULT_SENDER'] = settings.mail_default_sender
            
            # Update environment variables for compatibility with existing code
            os.environ['MAIL_SERVER'] = settings.mail_server
            os.environ['MAIL_PORT'] = str(settings.mail_port)
            os.environ['MAIL_USE_TLS'] = str(settings.mail_use_tls)
            os.environ['MAIL_USE_SSL'] = str(settings.mail_use_ssl)
            os.environ['MAIL_USERNAME'] = settings.mail_username
            os.environ['MAIL_PASSWORD'] = settings.mail_password
            os.environ['MAIL_DEFAULT_SENDER'] = settings.mail_default_sender
            
            # Update environment variable for mail notifications
            os.environ['BACKUP_NOTIFICATION_EMAILS'] = settings.notification_emails
            
            # Reinitialize mail
            mail.init_app(app)
            
            flash(_('E-posta ayarları başarıyla kaydedildi!'), 'success')
        except Exception as e:
            db.session.rollback()
            flash(f'E-posta ayarlarını kaydederken hata: {str(e)}', 'danger')
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f'{getattr(form, field).label.text}: {error}', 'danger')
    
    return redirect(url_for('settings'))


@app.route('/settings/encryption_key/add', methods=['POST'])
@login_required
def add_encryption_key():
    """Create a new GPG encryption key."""
    # Only admin users can create encryption keys
    if not current_user.is_admin:
        flash(_('GPG anahtarı oluşturma izniniz yok.'), 'danger')
        return redirect(url_for('settings'))
        
    form = EncryptionKeyForm()
    if form.validate_on_submit():
        try:
            # Import encryption utils
            from encryption_utils import generate_gpg_key
            
            # Generate GPG key pair
            key_result = generate_gpg_key(
                name=form.name.data,
                email=form.email.data,
                passphrase=form.passphrase.data if form.passphrase.data else None,
                key_type=form.key_type.data,
                key_length=form.key_length.data
            )
            
            if key_result:
                # Create database record
                from models import EncryptionKey
                new_key = EncryptionKey(
                    name=form.name.data,
                    email=form.email.data,
                    key_type=form.key_type.data,
                    key_length=form.key_length.data,
                    fingerprint=key_result.get('fingerprint', ''),
                    public_key=key_result.get('public_key', ''),
                    private_key=key_result.get('private_key', '') if form.store_private_key.data else None,
                    created_by=current_user.id
                )
                
                db.session.add(new_key)
                db.session.commit()
                flash(_('GPG şifreleme anahtarı başarıyla oluşturuldu!'), 'success')
            else:
                flash(_('GPG anahtarı oluşturulamadı.'), 'danger')
        except Exception as e:
            db.session.rollback()
            flash(f'GPG anahtarı oluşturma hatası: {str(e)}', 'danger')
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f'{getattr(form, field).label.text}: {error}', 'danger')
    
    return redirect(url_for('settings'))


@app.route('/settings/encryption_key/delete/<int:key_id>', methods=['POST'])
@login_required
def delete_encryption_key(key_id):
    """Delete a GPG encryption key."""
    # Only admin users can delete encryption keys
    if not current_user.is_admin:
        flash(_('GPG anahtarı silme izniniz yok.'), 'danger')
        return redirect(url_for('settings'))
        
    try:
        # Import encryption model
        from models import EncryptionKey
        encryption_key = EncryptionKey.query.get_or_404(key_id)
        
        # Check if key is used in any backup job
        jobs = BackupJob.query.filter_by(encryption_key_id=key_id).all()
        if jobs:
            flash(_('Yedekleme görevlerinde kullanılan GPG anahtarı silinemez.'), 'danger')
            return redirect(url_for('settings'))
        
        # Delete the key
        db.session.delete(encryption_key)
        db.session.commit()
        flash(_('GPG anahtarı başarıyla silindi!'), 'success')
    except SQLAlchemyError as e:
        db.session.rollback()
        flash(f'Veritabanı hatası: {str(e)}', 'danger')
    except Exception as e:
        flash(f'Hata: {str(e)}', 'danger')
    
    return redirect(url_for('settings'))


@app.route('/settings/test_email', methods=['POST'])
@login_required
def test_email():
    """Send a test email to verify email settings."""
    # Only admin users can send test emails
    if not current_user.is_admin:
        flash(_('Test e-postası gönderme izniniz yok.'), 'danger')
        return redirect(url_for('settings'))
        
    try:
        # Get email recipient(s)
        recipients_str = request.form.get('email_addresses', '')
        if not recipients_str:
            flash(_('Lütfen test e-postası göndermek için en az bir e-posta adresi girin.'), 'danger')
            return redirect(url_for('settings'))
        
        recipients = [email.strip() for email in recipients_str.split(',')]
        
        # Import and call the test email function
        from email_utils import send_test_email
        success = send_test_email(recipients)
        
        if success:
            flash(_('Test e-postası başarıyla gönderildi! Lütfen gelen kutunuzu kontrol edin.'), 'success')
        else:
            flash(_('Test e-postası gönderilemedi. Lütfen e-posta ayarlarınızı kontrol edin.'), 'danger')
            
    except Exception as e:
        flash(f'Test e-postası gönderme hatası: {str(e)}', 'danger')
    
    return redirect(url_for('settings'))

@app.route('/api/dashboard_stats')
def dashboard_stats():
    """API endpoint for dashboard statistics."""
    # Get backup success/failure counts for the last 7 days
    from sqlalchemy import func
    import datetime
    
    # Calculate date 7 days ago
    seven_days_ago = datetime.datetime.now() - datetime.timedelta(days=7)
    
    # Query logs to get counts by day and status
    daily_stats = db.session.query(
        func.date(BackupLog.timestamp).label('date'),
        BackupLog.status,
        func.count().label('count')
    ).filter(
        BackupLog.timestamp >= seven_days_ago
    ).group_by(
        func.date(BackupLog.timestamp),
        BackupLog.status
    ).all()
    
    # Format the data for Chart.js
    dates = sorted(list(set([stat.date.strftime('%Y-%m-%d') for stat in daily_stats])))
    success_data = [0] * len(dates)
    failed_data = [0] * len(dates)
    
    for stat in daily_stats:
        date_index = dates.index(stat.date.strftime('%Y-%m-%d'))
        if stat.status == 'success':
            success_data[date_index] = stat.count
        else:
            failed_data[date_index] = stat.count
    
    return jsonify({
        'labels': dates,
        'success': success_data,
        'failed': failed_data
    })

# Language and Theme Routes
@app.route('/set_language/<lang>')
def set_language(lang):
    """Set the user's preferred language."""
    if lang in ['en', 'tr']:
        session['lang'] = lang
        flash(_('Language changed successfully!'), 'success')
    return redirect(request.referrer or url_for('index'))

@app.route('/set_theme/<theme>')
def set_theme(theme):
    """Set the user's preferred theme."""
    # Tema değiştirme kaldırıldı, sadece light tema kullanılıyor
    session['theme'] = 'light'
    return redirect(request.referrer or url_for('index'))


@app.route('/login', methods=['GET', 'POST'])
def login():
    """Login page."""
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        
        if user is None or not user.check_password(form.password.data):
            flash(_('Geçersiz kullanıcı adı veya şifre'), 'danger')
            return redirect(url_for('login'))
        
        login_user(user, remember=form.remember_me.data)
        user.last_login = datetime.datetime.utcnow()
        db.session.commit()
        
        next_page = request.args.get('next')
        if not next_page or not next_page.startswith('/'):
            next_page = url_for('index')
            
        return redirect(next_page)
    
    return render_template('login.html', title=_('Giriş Yap'), form=form)


@app.route('/logout')
def logout():
    """Logout the current user."""
    logout_user()
    return redirect(url_for('login'))


# User management routes
@app.route('/users')
@login_required
def users():
    """Render the user management page."""
    # Only admins can access this page
    if not current_user.is_admin:
        flash(_('Bu sayfaya erişim izniniz yok.'), 'danger')
        return redirect(url_for('index'))
    
    users = User.query.all()
    form = RegistrationForm()
    
    return render_template('users.html', 
                          users=users, 
                          form=form,
                          current_user=current_user)

@app.route('/users/add', methods=['POST'])
@login_required
def add_user():
    """Add a new user."""
    # Only admins can add users
    if not current_user.is_admin:
        flash(_('Kullanıcı ekleme izniniz yok.'), 'danger')
        return redirect(url_for('index'))
    
    form = RegistrationForm()
    if form.validate_on_submit():
        try:
            new_user = User(
                username=form.username.data,
                email=form.email.data,
                is_admin=form.is_admin.data
            )
            new_user.set_password(form.password.data)
            
            db.session.add(new_user)
            db.session.commit()
            flash(_('Kullanıcı başarıyla eklendi!'), 'success')
        except SQLAlchemyError as e:
            db.session.rollback()
            flash(f'Veritabanı hatası: {str(e)}', 'danger')
        except Exception as e:
            flash(f'Hata: {str(e)}', 'danger')
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f'{getattr(form, field).label.text}: {error}', 'danger')
    
    return redirect(url_for('users'))

@app.route('/users/delete/<int:user_id>', methods=['POST'])
@login_required
def delete_user(user_id):
    """Delete a user."""
    # Only admins can delete users
    if not current_user.is_admin:
        flash(_('Kullanıcı silme izniniz yok.'), 'danger')
        return redirect(url_for('index'))
    
    try:
        user = User.query.get_or_404(user_id)
        
        # Prevent deleting the admin user or yourself
        if user.username == 'admin':
            flash(_('Ana admin kullanıcısı silinemez.'), 'danger')
            return redirect(url_for('users'))
        
        if user.id == current_user.id:
            flash(_('Kendinizi silemezsiniz.'), 'danger')
            return redirect(url_for('users'))
        
        db.session.delete(user)
        db.session.commit()
        flash(_('Kullanıcı başarıyla silindi!'), 'success')
    except SQLAlchemyError as e:
        db.session.rollback()
        flash(f'Veritabanı hatası: {str(e)}', 'danger')
    except Exception as e:
        flash(f'Hata: {str(e)}', 'danger')
    
    return redirect(url_for('users'))

@app.route('/users/toggle_admin/<int:user_id>', methods=['POST'])
@login_required
def toggle_user_admin(user_id):
    """Toggle admin status for a user."""
    # Only admins can change admin status
    if not current_user.is_admin:
        flash(_('Yetki değiştirme izniniz yok.'), 'danger')
        return redirect(url_for('index'))
    
    try:
        user = User.query.get_or_404(user_id)
        
        # Prevent changing admin status of the main admin user
        if user.username == 'admin':
            flash(_('Ana admin kullanıcısının yetkisi değiştirilemez.'), 'danger')
            return redirect(url_for('users'))
        
        user.is_admin = not user.is_admin
        db.session.commit()
        
        if user.is_admin:
            flash(f'{user.username} kullanıcısına admin yetkisi verildi.', 'success')
        else:
            flash(f'{user.username} kullanıcısının admin yetkisi kaldırıldı.', 'success')
            
    except SQLAlchemyError as e:
        db.session.rollback()
        flash(f'Veritabanı hatası: {str(e)}', 'danger')
    except Exception as e:
        flash(f'Hata: {str(e)}', 'danger')
    
    return redirect(url_for('users'))


# Register endpoint kaldırıldı

# Alternatif olarak, ilk kullanıcıyı oluşturmak için bir komut satırı aracı:
def create_admin_user(username, email, password):
    """
    Komut satırı aracından çağrılmak üzere tasarlanmıştır.
    İlk admin kullanıcıyı oluşturur.
    """
    # İlk kullanıcı kontrolü
    if User.query.count() > 0:
        return False, "Sistemde zaten kullanıcılar bulunuyor!"
    
    try:
        user = User(username=username, email=email, is_admin=True)
        user.set_password(password)
        db.session.add(user)
        db.session.commit()
        return True, f"Admin kullanıcı {username} başarıyla oluşturuldu!"
    except Exception as e:
        db.session.rollback()
        return False, f"Hata: {str(e)}"

# Veritabanı Performans Yönetimi Sayfaları
@app.route('/performance')
@login_required
def performance_dashboard():
    """Render the performance dashboard page."""
    # Desteklenen veritabanlarını getir
    db_connections = []
    
    # PostgreSQL veritabanlarını getir
    pg_dbs = DatabaseConnection.query.filter_by(db_type='postgresql').all()
    for db in pg_dbs:
        db.performance_supported = True
        db.performance_type = 'postgresql'
        db_connections.append(db)
    
    # MySQL/MariaDB veritabanlarını getir (performans desteği mevcutsa)
    if MYSQL_PERFORMANCE_AVAILABLE:
        mysql_dbs = DatabaseConnection.query.filter(
            (DatabaseConnection.db_type == 'mysql') | 
            (DatabaseConnection.db_type == 'mariadb')
        ).all()
        
        for db in mysql_dbs:
            db.performance_supported = True
            db.performance_type = 'mysql'
            db_connections.append(db)
    
    # Create a simple CSRF form for database operations
    from flask_wtf import FlaskForm
    csrf_form = FlaskForm()
    
    return render_template('performance/dashboard.html', 
                          db_connections=db_connections,
                          csrf_form=csrf_form,
                          mysql_supported=MYSQL_PERFORMANCE_AVAILABLE)

@app.route('/performance/queries/<int:db_id>')
@login_required
def performance_queries(db_id):
    """Veritabanı sorgu performans verilerini göster."""
    # Doğrudan grafik görünümüne yönlendir (tüm veritabanları için)
    if request.args.get('view', 'chart') == 'chart':
        return redirect(url_for('performance_charts', db_id=db_id))
    
    try:
        # Veritabanı bağlantısını al
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        # Sorgu parametrelerini al
        limit = request.args.get('limit', 20, type=int)
        order_by = request.args.get('order_by', 'total_time')
        interval = request.args.get('interval', None)
        
        # Veritabanı bilgilerini hazırla
        db_info = {
            'host': db_conn.host,
            'port': db_conn.port,
            'username': db_conn.username,
            'password': db_conn.password,
            'database': db_conn.database
        }
        
        # Veritabanı tipine göre uygun fonksiyonu çağır
        if db_conn.db_type == 'postgresql':
            # Sabit formatlama kullanan database_performance_fixed modülünü kullan
            from database_performance_fixed import get_top_queries as pg_get_top_queries
            result = pg_get_top_queries(db_info, limit, order_by, interval)
            
            # pg_stat_statements etkin değilse bilgi mesajı göster
            if not result.get('success', False) and 'pg_stat_statements modülü etkin değil' in result.get('message', ''):
                # CSRF form for module activation
                from flask_wtf import FlaskForm
                csrf_form = FlaskForm()
                return render_template('performance/missing_pgss.html', 
                                     db_conn=db_conn,
                                     message=result.get('message', ''),
                                     csrf_form=csrf_form)
        
        elif db_conn.db_type in ['mysql', 'mariadb'] and MYSQL_PERFORMANCE_AVAILABLE:
            # mysql_performance modülünden import edilmiş fonksiyonları kullan
            from mysql_performance import get_top_queries as mysql_get_top_queries
            result = mysql_get_top_queries(db_info, limit, order_by, interval)
            
            # Performance Schema etkin değilse bilgi mesajı göster
            if not result.get('success', False) and 'Performance Schema etkin değil' in result.get('message', ''):
                # CSRF form for module activation
                from flask_wtf import FlaskForm
                csrf_form = FlaskForm()
                return render_template('performance/missing_pgss.html', 
                                     db_conn=db_conn,
                                     message=result.get('message', ''),
                                     csrf_form=csrf_form)
        else:
            flash(_('Sorgu performansı bu veritabanı türü için desteklenmiyor.'), 'warning')
            return redirect(url_for('performance_dashboard'))
        
        # Hata durumunda bilgi mesajı göster
        if not result.get('success', False):
            flash(result.get('message', 'Bilinmeyen bir hata oluştu'), 'danger')
            return redirect(url_for('performance_dashboard'))
        
        # Veri varsa sonuçları render et
        pg_version = None
        mysql_version = None
        
        if db_conn.db_type == 'postgresql':
            pg_version = result.get('pg_version', 'N/A')  # pg_version anahtarını kullan
        elif db_conn.db_type in ['mysql', 'mariadb']:
            mysql_version = result.get('version', 'N/A')
        
        # `summary` nesnesinde None değer olma ihtimaline karşı güvenlik önlemi
        summary = result.get('summary', {})
        if summary is None:
            summary = {}
        
        # Debug için loglar
        app.logger.info(f"Sorgu sonucu anahtarları: {result.keys()}")
        app.logger.info(f"PostgreSQL versiyonu: {pg_version}")
        app.logger.info(f"Özet veri: {summary}")
        
        # Veri alanlarını kontrol et ve hata durumunda detaylı log
        try:
            for query_data in result.get('data', []):
                # Kilit alanları kontrol et
                app.logger.info(f"Sorgu veri örneği: {query_data}")
                break  # Sadece ilk öğeyi log'la
        except Exception as e:
            app.logger.error(f"Sorgu verisi işlenirken hata: {e}")
        
        # Format ve güvenlik uyarısı varsa kullanıcıya daha bilgi verici bir mesaj göster
        if not result.get('data'):
            flash('Sorgu performans verileri alındı ancak sonuç bulunamadı. SQL sorguları henüz kaydedilmemiş olabilir.', 'warning')
        
        try:
            # Tüm veriyi doğru formatta olduğundan emin olacak şekilde ön-işle
            processed_queries = []
            chart_data = {
                "labels": [],  # Sorgu metinlerinden kısaltılmış etiketler
                "cpu_percentages": [], # CPU yüzdeleri (sayısal değerler)
                "time_values": [],    # Toplam çalışma süresi (ms cinsinden sayısal değerler)
                "calls": []           # Çağrı sayıları
            }
            
            for query in result.get('data', []):
                # Orijinal veriyi değiştirmemek için kopyasını al
                processed_query = query.copy()  
                
                # 1. Tüm alanları ön-kontrol ve temizleme
                for key in processed_query:
                    if processed_query[key] is None:
                        # None değerler için varsayılan değerler
                        if key in ['total_time', 'avg_time']:
                            processed_query[key] = "0.00 ms"
                        elif key == 'percentage_cpu':
                            processed_query[key] = "0.00%"
                        elif key in ['calls', 'rows']:
                            processed_query[key] = 0
                        elif key == 'query':
                            processed_query[key] = "Unknown query"
                        else:
                            processed_query[key] = ""
                
                # 2. Özel alanlar için format doğrulama ve dönüşüm
                # Yüzde işareti içeren alanları HTML güvenli gösterimi için işaretle
                if 'percentage_cpu' in processed_query:
                    cpu_value = 0.0
                    try:
                        # Tür ve format düzeltme
                        if isinstance(processed_query['percentage_cpu'], str):
                            # Eğer zaten string ise, sayısal değeri çıkar
                            if '%' in processed_query['percentage_cpu']:
                                cpu_value = float(processed_query['percentage_cpu'].replace('%', '').strip())
                            else:
                                cpu_value = float(processed_query['percentage_cpu'])
                        else:
                            # Sayısal değer ise direkt dönüştür
                            cpu_value = float(processed_query['percentage_cpu'])
                        
                        # Grafik için sayısal değeri kaydet
                        chart_data["cpu_percentages"].append(cpu_value)
                        # Görüntüleme için güvenli string değere çevir - f-string yerine format() kullan
                        processed_query['percentage_cpu'] = "{:.2f}%".format(cpu_value)
                    except (ValueError, TypeError):
                        chart_data["cpu_percentages"].append(0.0)
                        processed_query['percentage_cpu'] = "0.00%"
                
                # Toplam çalışma süresi - sayısal değer çıkar
                if 'total_time' in processed_query:
                    time_value = 0.0
                    try:
                        if isinstance(processed_query['total_time'], str):
                            # "XX.XX ms" formatından sayısal değeri çıkar
                            if 'ms' in processed_query['total_time']:
                                time_value = float(processed_query['total_time'].replace('ms', '').strip())
                            else:
                                time_value = float(processed_query['total_time'])
                        else:
                            time_value = float(processed_query['total_time'])
                        
                        chart_data["time_values"].append(time_value)
                    except (ValueError, TypeError):
                        chart_data["time_values"].append(0.0)
                else:
                    chart_data["time_values"].append(0.0)
                    
                # Çağrı sayısı
                if 'calls' in processed_query:
                    try:
                        calls = int(processed_query['calls'])
                        chart_data["calls"].append(calls)
                    except (ValueError, TypeError):
                        chart_data["calls"].append(0)
                else:
                    chart_data["calls"].append(0)
                
                # Sorgu metni/etiketi
                if 'query' in processed_query and processed_query['query']:
                    # Sorgu metnini kısalt, grafik için etiket olarak kullan
                    query_text = processed_query['query']
                    chart_label = query_text[:20] + "..." if len(query_text) > 20 else query_text
                    chart_data["labels"].append(chart_label)
                else:
                    chart_data["labels"].append("Unknown")
                
                processed_queries.append(processed_query)
                
            # Chart.js için veri hazırla - JSON olarak gönderebilmek için güvenli hale getir
            # Format string hatalarını önlemek için değerleri string yerine sadece sayısal değerler olarak gönder
            try:
                # Tamamen yeni bir veri yapısı oluştur
                import json
                # Yüzde işaretini içeren metinsel değerler yerine sayısal değerler kullan
                clean_chart_data = {
                    "labels": [str(i) for i in range(len(chart_data["labels"]))],  # Sadece indis numaralarını gönder
                    "cpu_percentages": chart_data["cpu_percentages"],
                    "time_values": chart_data["time_values"], 
                    "calls": chart_data["calls"],
                    "original_labels": chart_data["labels"] # Orijinal etiketleri farklı bir alanda gönder
                }
                
                # Güvenli JSON'a dönüştür - hata yönetimi ekle
                try:
                    chart_data_json = json.dumps(clean_chart_data)
                except Exception as e:
                    app.logger.error(f"JSON serileştirme hatası: {e}")
                    # Hata durumunda boş veri yapısı
                    chart_data_json = "{}"
                
                # İşlenmiş verileri template'e aktar
                return render_template('performance/queries.html', 
                                    db_conn=db_conn,
                                    queries=processed_queries,
                                    summary=summary,
                                    pg_version=pg_version,
                                    mysql_version=mysql_version,
                                    order_by=order_by,
                                    limit=limit,
                                    interval=interval,
                                    chart_data=chart_data_json)
            except Exception as json_error:
                app.logger.error(f"JSON serileştirmede hata: {json_error}")
                # JSON dönüşümünde hata varsa, grafiksiz şablonu göster
                return render_template('performance/queries.html', 
                                    db_conn=db_conn,
                                    queries=processed_queries,
                                    summary=summary,
                                    pg_version=pg_version,
                                    mysql_version=mysql_version,
                                    order_by=order_by,
                                    limit=limit,
                                    interval=interval,
                                    chart_data="{}")
        except Exception as format_error:
            # Şablon oluşturulurken bir format hatası olursa daha açıklayıcı bir hata mesajı göster
            app.logger.error(f"Şablon oluşturulurken format hatası: {format_error}")
            flash('Performans verileri işlenirken bir format hatası oluştu. Veriler basitleştirilmiş formatta gösterilecek.', 'warning')
            
            # Daha güvenli bir şekilde f-string kullan - süslü parantezleri escape et
            escaped_name = db_conn.name.replace('{', '{{').replace('}', '}}')
            safe_total_calls = str(summary.get('total_calls', 'N/A')).replace('{', '{{').replace('}', '}}')
            safe_query_count = str(summary.get('query_count', 'N/A')).replace('{', '{{').replace('}', '}}')
            safe_total_time = str(summary.get('total_time', 'N/A')).replace('{', '{{').replace('}', '}}')
            
            # Şablon hatası durumunda basitleştirilmiş bir görünüm kullanarak verileri göster
            # f-string yerine format() metodunu kullan
            simple_view = """
            <div class="container my-4">
                <h1>Sorgu Performansı: {escaped_name}</h1>
                <div class="alert alert-warning">
                    <strong>Uyarı:</strong> Performans verileri işlenirken bir format hatası oluştu.
                    Veriler basitleştirilmiş formatta görüntüleniyor.
                </div>
                <div class="card mb-4">
                    <div class="card-header">Özet Bilgiler</div>
                    <div class="card-body">
                        <p>Toplam Çağrı: {safe_total_calls}</p>
                        <p>Sorgu Sayısı: {safe_query_count}</p>
                        <p>Toplam Çalışma Süresi: {safe_total_time}</p>
                    </div>
                </div>
                <a href="{grafik_link}" class="btn btn-success">Grafik Görünümüne Geç</a>
                <a href="{back_link}" class="btn btn-secondary">Geri Dön</a>
            </div>
            """.format(
                escaped_name=escaped_name,
                safe_total_calls=safe_total_calls,
                safe_query_count=safe_query_count,
                safe_total_time=safe_total_time,
                back_link=url_for('performance_dashboard'),
                grafik_link=url_for('performance_charts', db_id=db_conn.id)
            )
            return simple_view
    
    except Exception as e:
        app.logger.error(f"Performans verileri alınırken kritik hata: {str(e)}")
        flash(f'Performans verileri alınırken hata: {str(e)}', 'danger')
        return redirect(url_for('performance_dashboard'))

@app.route('/performance/database/<int:db_id>')
@login_required
def performance_database(db_id):
    """Veritabanı istatistiklerini göster."""
    try:
        # Veritabanı bağlantısını al
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        # Veritabanı bilgilerini hazırla
        db_info = {
            'host': db_conn.host,
            'port': db_conn.port,
            'username': db_conn.username,
            'password': db_conn.password,
            'database': db_conn.database
        }
        
        # Veritabanı tipine göre uygun fonksiyonu çağır
        if db_conn.db_type == 'postgresql':
            # Sabit formatlama kullanan database_performance_fixed modülünü kullan
            from database_performance_fixed import get_database_stats as pg_get_database_stats
            result = pg_get_database_stats(db_info)
        elif db_conn.db_type in ['mysql', 'mariadb'] and MYSQL_PERFORMANCE_AVAILABLE:
            # mysql_performance modülünden import edilmiş fonksiyonları kullan
            from mysql_performance import get_database_stats as mysql_get_database_stats
            result = mysql_get_database_stats(db_info)
        else:
            flash(_('Veritabanı istatistikleri bu veritabanı türü için desteklenmiyor.'), 'warning')
            return redirect(url_for('performance_dashboard'))
        
        # Hata durumunda bilgi mesajı göster
        if not result.get('success', False):
            flash(result.get('message', 'Bilinmeyen bir hata oluştu'), 'danger')
            return redirect(url_for('performance_dashboard'))
        
        # Veri varsa sonuçları render et
        return render_template('performance/database.html', 
                             db_conn=db_conn,
                             stats=result.get('data', {}),
                             is_mysql=(db_conn.db_type in ['mysql', 'mariadb']))
    
    except Exception as e:
        app.logger.error(f"Veritabanı istatistikleri alınırken hata: {str(e)}")
        flash(f'Veritabanı istatistikleri alınırken hata: {str(e)}', 'danger')
        return redirect(url_for('performance_dashboard'))

@app.route('/performance/charts/<int:db_id>')
@login_required
def performance_charts(db_id):
    """Sorgu performans grafiklerini göster."""
    try:
        # Veritabanı bağlantısını al
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        # Sorgu parametrelerini al
        limit = int(request.args.get('limit', 10))
        order_by = request.args.get('order_by', 'total_exec_time')
        
        # Veritabanı tipine göre uygun modülü seç
        try:
            # Zaman damgası oluştur - yeni grafikleri eski grafiklerden ayırt etmek için
            timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
            
            # Veritabanı tipine göre uygun modülü seç ve grafikleri oluştur
            if db_conn.db_type == 'postgresql':
                import performance_charts
                result = performance_charts.main(db_id, limit, order_by)
            elif db_conn.db_type in ['mysql', 'mariadb']:
                import mysql_performance_charts
                result = mysql_performance_charts.main(db_id, limit, order_by)
            else:
                flash('Bu veritabanı tipi için grafik görünümü desteklenmiyor.', 'warning')
                return redirect(url_for('performance_queries', db_id=db_id))
            
            if not result or not result.get('charts'):
                if db_conn.db_type == 'postgresql':
                    flash('Grafikler oluşturulamadı. Lütfen pg_stat_statements modülünün etkin olduğundan emin olun.', 'warning')
                elif db_conn.db_type in ['mysql', 'mariadb']:
                    flash('Grafikler oluşturulamadı. Lütfen performance_schema özelliğinin etkin olduğundan emin olun.', 'warning')
                else:
                    flash('Grafikler oluşturulamadı.', 'warning')
                return redirect(url_for('performance_queries', db_id=db_id))
                
            # Grafik yolları ve özet verileri
            chart_files = result['charts']
            summary = result['summary']
            query_count = result['query_count']
            
            # Statik URL'lere dönüştür
            chart_urls = {}
            for key, path in chart_files.items():
                if os.path.exists(path):
                    # URL yolu olarak dönüştür (/static/... şeklinde)
                    url = '/' + os.path.relpath(path)
                    chart_urls[key] = url
            
            # Şablonu render et
            return render_template(
                'performance/query_charts.html',
                db_conn=db_conn,
                chart_urls=chart_urls,
                summary=summary,
                query_count=query_count,
                limit=limit,
                order_by=order_by,
                timestamp=timestamp
            )
            
        except ImportError as ie:
            app.logger.error(f"Pandas veya Matplotlib kütüphaneleri eksik: {str(ie)}")
            flash('Grafik oluşturma için gerekli kütüphaneler eksik.', 'danger')
            return redirect(url_for('performance_queries', db_id=db_id))
            
    except Exception as e:
        app.logger.error(f"Grafik sayfası yüklenirken hata: {str(e)}")
        flash(f'Grafik sayfası yüklenirken hata: {str(e)}', 'danger')
        return redirect(url_for('performance_dashboard'))

@app.route('/performance/table/<int:db_id>/<table_name>')
@login_required
def performance_table(db_id, table_name):
    """Tablo istatistiklerini göster."""
    try:
        # Veritabanı bağlantısını al
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        # Veritabanı bilgilerini hazırla
        db_info = {
            'host': db_conn.host,
            'port': db_conn.port,
            'username': db_conn.username,
            'password': db_conn.password,
            'database': db_conn.database
        }
        
        # Veritabanı tipine göre uygun fonksiyonu çağır
        if db_conn.db_type == 'postgresql':
            # Sabit formatlama kullanan database_performance_fixed modülünü kullan
            from database_performance_fixed import get_table_stats as pg_get_table_stats
            result = pg_get_table_stats(db_info, table_name)
        elif db_conn.db_type in ['mysql', 'mariadb'] and MYSQL_PERFORMANCE_AVAILABLE:
            # mysql_performance modülünden import edilmiş fonksiyonları kullan
            from mysql_performance import get_table_stats as mysql_get_table_stats
            result = mysql_get_table_stats(db_info, table_name)
        else:
            flash(_('Tablo istatistikleri bu veritabanı türü için desteklenmiyor.'), 'warning')
            return redirect(url_for('performance_dashboard'))
        
        # Hata durumunda bilgi mesajı göster
        if not result.get('success', False):
            flash(result.get('message', 'Bilinmeyen bir hata oluştu'), 'danger')
            return redirect(url_for('performance_database', db_id=db_id))
        
        # Veri varsa sonuçları render et
        return render_template('performance/table.html', 
                             db_conn=db_conn,
                             table_name=table_name,
                             stats=result.get('data', {}),
                             is_mysql=(db_conn.db_type in ['mysql', 'mariadb']))
    
    except Exception as e:
        app.logger.error(f"Tablo istatistikleri alınırken hata: {str(e)}")
        flash(f'Tablo istatistikleri alınırken hata: {str(e)}', 'danger')
        return redirect(url_for('performance_database', db_id=db_id))

@app.route('/performance/enable_pgss/<int:db_id>', methods=['POST'])
@login_required
def enable_pgss(db_id):
    """PostgreSQL pg_stat_statements modülünü etkinleştir."""
    # Admin yetkisi kontrolü
    if not current_user.can_manage_db_connections():
        flash(_('Veritabanı modüllerini etkinleştirme izniniz yok.'), 'danger')
        return redirect(url_for('performance_dashboard'))
    
    try:
        # Veritabanı bağlantısını al
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        # Sadece PostgreSQL'i destekle
        if db_conn.db_type != 'postgresql':
            flash(_('pg_stat_statements sadece PostgreSQL için desteklenir.'), 'warning')
            return redirect(url_for('performance_dashboard'))
        
        # Modülü etkinleştir
        conn_str = "host={} port={} user={} password={} dbname={}".format(db_conn.host, db_conn.port, db_conn.username, db_conn.password, db_conn.database)
        connection = psycopg2.connect(conn_str)
        
        from database_performance_fixed import enable_pg_stat_statements
        result = enable_pg_stat_statements(connection)
        
        # Sonucu göster
        if result.get('success', False):
            flash(result.get('message', 'Modül başarıyla etkinleştirildi.'), 'success')
        else:
            flash(result.get('message', 'Modül etkinleştirilemedi.'), 'danger')
    
    except Exception as e:
        flash('pg_stat_statements etkinleştirilirken hata: {}'.format(str(e)), 'danger')
    
    return redirect(url_for('performance_queries', db_id=db_id))

@app.route('/performance/enable_performance_schema/<int:db_id>', methods=['POST'])
@login_required
def enable_performance_schema_route(db_id):
    """MySQL Performance Schema'yı etkinleştir."""
    # Admin yetkisi kontrolü
    if not current_user.can_manage_db_connections():
        flash(_('Veritabanı modüllerini etkinleştirme izniniz yok.'), 'danger')
        return redirect(url_for('performance_dashboard'))
    
    # MySQL Performance desteği mevcut mu kontrol et
    if not MYSQL_PERFORMANCE_AVAILABLE:
        flash(_('MySQL performans modülleri kullanılamıyor.'), 'warning')
        return redirect(url_for('performance_dashboard'))
    
    try:
        # Veritabanı bağlantısını al
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        # Sadece MySQL/MariaDB'yi destekle
        if db_conn.db_type not in ['mysql', 'mariadb']:
            flash(_('Performance Schema sadece MySQL ve MariaDB için desteklenir.'), 'warning')
            return redirect(url_for('performance_dashboard'))
        
        # Veritabanı bilgilerini hazırla
        db_info = {
            'host': db_conn.host,
            'port': db_conn.port,
            'username': db_conn.username,
            'password': db_conn.password,
            'database': db_conn.database
        }
        
        # Modülü etkinleştir
        from mysql_performance import enable_performance_schema
        result = enable_performance_schema(db_info)
        
        # Sonucu göster
        if result.get('success', False):
            flash(result.get('message', 'Performance Schema başarıyla etkinleştirildi.'), 'success')
        else:
            flash(result.get('message', 'Performance Schema etkinleştirilemedi.'), 'danger')
    
    except Exception as e:
        flash('Performance Schema etkinleştirilirken hata: {}'.format(str(e)), 'danger')
    
    return redirect(url_for('performance_queries', db_id=db_id))

@app.route('/performance/reset_stats/<int:db_id>', methods=['POST'])
@login_required
def reset_stats(db_id):
    """Veritabanı sorgu istatistiklerini sıfırla."""
    # Admin yetkisi kontrolü
    if not current_user.can_manage_db_connections():
        flash(_('Sorgu istatistiklerini sıfırlama izniniz yok.'), 'danger')
        return redirect(url_for('performance_dashboard'))
    
    try:
        # Veritabanı bağlantısını al
        db_conn = DatabaseConnection.query.get_or_404(db_id)
        
        # Sorgu parametrelerini al
        reset_limit = int(request.args.get('limit', 20))
        reset_order_by = request.args.get('order_by', 'total_time')
        
        # Verileri sıfırla 
        db_info = {
            'host': db_conn.host,
            'port': db_conn.port,
            'username': db_conn.username,
            'password': db_conn.password,
            'database': db_conn.database
        }
        
        # Veritabanı tipine göre uygun fonksiyonu çağır
        if db_conn.db_type == 'postgresql':
            from database_performance_fixed import get_top_queries as pg_get_top_queries
            result = pg_get_top_queries(db_info, reset_limit, reset_order_by, 'reset')
        elif db_conn.db_type in ['mysql', 'mariadb'] and MYSQL_PERFORMANCE_AVAILABLE:
            from mysql_performance import get_top_queries as mysql_get_top_queries
            result = mysql_get_top_queries(db_info, reset_limit, reset_order_by, 'reset')
        else:
            flash(_('Sorgu istatistikleri sıfırlama bu veritabanı türü için desteklenmiyor.'), 'warning')
            return redirect(url_for('performance_dashboard'))
        
        # Sonucu göster
        if result.get('success', False):
            flash(_('Sorgu istatistikleri başarıyla sıfırlandı.'), 'success')
        else:
            flash(result.get('message', 'İstatistikler sıfırlanamadı.'), 'danger')
    
    except Exception as e:
        flash('Sorgu istatistikleri sıfırlanırken hata: {}'.format(str(e)), 'danger')
        reset_limit = int(request.args.get('limit', 20))
        reset_order_by = request.args.get('order_by', 'total_time')
    
    return redirect(url_for('performance_queries', db_id=db_id, limit=reset_limit, order_by=reset_order_by))

© KUJUNTI.ID