427 lines
16 KiB
HTML
427 lines
16 KiB
HTML
{% extends 'ssh_manager/base.html' %}
|
||
|
||
{% block title %}Yedeklemeler - Hosting Yönetim Paneli{% endblock %}
|
||
|
||
{% block content %}
|
||
<!-- Yedeklemeler Header -->
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<div>
|
||
<h3 class="mb-1">
|
||
<i class="bi bi-cloud-arrow-up me-2"></i>Yedeklemeler
|
||
</h3>
|
||
<small class="text-muted">Proje yedekleme işlemleri ve S3 yönetimi</small>
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<button class="btn btn-info" onclick="refreshBackupStatus()">
|
||
<i class="bi bi-arrow-clockwise"></i> Durumu Yenile
|
||
</button>
|
||
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#backupModal">
|
||
<i class="bi bi-cloud-arrow-up"></i> Yeni Yedekleme
|
||
</button>
|
||
<button class="btn btn-success" onclick="startAllBackups()">
|
||
<i class="bi bi-cloud-arrow-up-fill"></i> Tümünü Yedekle
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Yedekleme İstatistikleri -->
|
||
<div class="row g-4 mb-4">
|
||
<div class="col-md-3">
|
||
<div class="card">
|
||
<div class="card-body text-center">
|
||
<div class="icon-box bg-info bg-opacity-10 mx-auto mb-3">
|
||
<i class="bi bi-cloud-arrow-up text-info"></i>
|
||
</div>
|
||
<h4 class="mb-1">{{ total_backups|default:0 }}</h4>
|
||
<p class="text-muted mb-0">Toplam Yedekleme</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="card">
|
||
<div class="card-body text-center">
|
||
<div class="icon-box bg-success bg-opacity-10 mx-auto mb-3">
|
||
<i class="bi bi-check-circle text-success"></i>
|
||
</div>
|
||
<h4 class="mb-1">{{ successful_backups|default:0 }}</h4>
|
||
<p class="text-muted mb-0">Başarılı</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="card">
|
||
<div class="card-body text-center">
|
||
<div class="icon-box bg-danger bg-opacity-10 mx-auto mb-3">
|
||
<i class="bi bi-x-circle text-danger"></i>
|
||
</div>
|
||
<h4 class="mb-1">{{ failed_backups|default:0 }}</h4>
|
||
<p class="text-muted mb-0">Başarısız</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="card">
|
||
<div class="card-body text-center">
|
||
<div class="icon-box bg-warning bg-opacity-10 mx-auto mb-3">
|
||
<i class="bi bi-clock text-warning"></i>
|
||
</div>
|
||
<h4 class="mb-1">
|
||
{% if backup_logs %}
|
||
{{ backup_logs.0.created_at|date:"d.m H:i" }}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</h4>
|
||
<p class="text-muted mb-0">Son Yedekleme</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Yedekleme Geçmişi -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5 class="card-title mb-0">
|
||
<i class="bi bi-clock-history me-2"></i>Yedekleme Geçmişi
|
||
</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if backup_logs %}
|
||
<div class="table-responsive">
|
||
<table class="table table-dark table-striped">
|
||
<thead>
|
||
<tr>
|
||
<th>Tarih</th>
|
||
<th>Proje</th>
|
||
<th>Host</th>
|
||
<th>Durum</th>
|
||
<th>Detay</th>
|
||
<th class="actions">İşlemler</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for log in backup_logs %}
|
||
<tr>
|
||
<td>
|
||
<span>{{ log.created_at|date:"d.m.Y" }}</span><br>
|
||
<small class="text-muted">{{ log.created_at|date:"H:i:s" }}</small>
|
||
</td>
|
||
<td>
|
||
{% if log.ssh_credential %}
|
||
<strong>{{ log.ssh_credential.name }}</strong>
|
||
{% else %}
|
||
<span class="text-muted">Genel</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if log.ssh_credential %}
|
||
<small>{{ log.ssh_credential.hostname }}</small>
|
||
{% else %}
|
||
<small class="text-muted">-</small>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<span class="badge {% if log.status == 'success' %}bg-success{% elif log.status == 'error' %}bg-danger{% else %}bg-warning{% endif %} fs-6">
|
||
{% if log.status == 'success' %}
|
||
<i class="bi bi-check-circle me-1"></i>Başarılı
|
||
{% elif log.status == 'error' %}
|
||
<i class="bi bi-x-circle me-1"></i>Hata
|
||
{% else %}
|
||
<i class="bi bi-clock me-1"></i>Bekliyor
|
||
{% endif %}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
{% if log.output %}
|
||
<button class="btn btn-sm btn-outline-info" onclick="showLogDetail('{{ log.output|escapejs }}', '{{ log.created_at|date:"d.m.Y H:i" }}')">
|
||
<i class="bi bi-eye"></i> Detay
|
||
</button>
|
||
{% else %}
|
||
<span class="text-muted">-</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<div class="btn-group" role="group">
|
||
{% if log.ssh_credential %}
|
||
<button class="btn btn-sm btn-outline-warning" onclick="retryBackup({{ log.ssh_credential.id }})" title="Tekrar Dene">
|
||
<i class="bi bi-arrow-clockwise"></i>
|
||
</button>
|
||
{% endif %}
|
||
<button class="btn btn-sm btn-outline-danger" onclick="deleteLog({{ log.id }})" title="Kaydı Sil">
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="text-center py-5">
|
||
<i class="bi bi-cloud-arrow-up" style="font-size: 3rem; color: #6c757d;"></i>
|
||
<h5 class="mt-3 text-muted">Henüz yedekleme yapılmamış</h5>
|
||
<p class="text-muted">İlk yedeklemenizi başlatmak için yukarıdaki butonu kullanın</p>
|
||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#backupModal">
|
||
<i class="bi bi-cloud-arrow-up"></i> İlk Yedeklemeyi Başlat
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Yedekleme Modal -->
|
||
<div class="modal fade" id="backupModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content bg-dark border-secondary">
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">
|
||
<i class="bi bi-cloud-arrow-up me-2"></i>Yeni Yedekleme
|
||
</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="backupForm">
|
||
{% csrf_token %}
|
||
<div class="mb-3">
|
||
<label for="backupProject" class="form-label">Proje Seçin</label>
|
||
<select class="form-select bg-dark text-white border-secondary" id="backupProject" name="project_id" required>
|
||
<option value="">Proje seçin...</option>
|
||
{% load ssh_manager_tags %}
|
||
{% get_projects as projects %}
|
||
{% for project in projects %}
|
||
<option value="{{ project.id }}">{{ project.name }} ({{ project.ssh_credential.hostname }})</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="backupType" class="form-label">Yedekleme Türü</label>
|
||
<select class="form-select bg-dark text-white border-secondary" id="backupType" name="backup_type">
|
||
<option value="full">Tam Yedekleme</option>
|
||
<option value="files">Sadece Dosyalar</option>
|
||
<option value="database">Sadece Veritabanı</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="compressBackup" name="compress" checked>
|
||
<label class="form-check-label" for="compressBackup">
|
||
Sıkıştırılmış yedekleme (.tar.gz)
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="backupNote" class="form-label">Not (Opsiyonel)</label>
|
||
<textarea class="form-control bg-dark text-white border-secondary" id="backupNote" name="note" rows="3" placeholder="Yedekleme hakkında not..."></textarea>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
|
||
<button type="button" class="btn btn-warning" onclick="startBackup()">
|
||
<i class="bi bi-cloud-arrow-up"></i> Yedeklemeyi Başlat
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Log Detay Modal -->
|
||
<div class="modal fade" id="logDetailModal" tabindex="-1">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content bg-dark border-secondary">
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">
|
||
<i class="bi bi-file-text me-2"></i>Yedekleme Detayı
|
||
</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="logDetailContent" style="font-family: monospace; background: #1a1d23; padding: 1rem; border-radius: 0.375rem; max-height: 400px; overflow-y: auto;">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Kapat</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.icon-box {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.icon-box i {
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.card {
|
||
background: #23272b;
|
||
border: 1px solid #444;
|
||
}
|
||
|
||
.card-header {
|
||
background: #1a1d23;
|
||
border-bottom: 1px solid #444;
|
||
}
|
||
|
||
.table-dark {
|
||
--bs-table-bg: #23272b;
|
||
}
|
||
|
||
.badge {
|
||
font-size: 0.8rem;
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
// Yedekleme durumunu yenile
|
||
function refreshBackupStatus() {
|
||
showToast('Yedekleme durumu kontrol ediliyor...', 'info');
|
||
setTimeout(() => location.reload(), 1000);
|
||
}
|
||
|
||
// Tüm projeleri yedekle
|
||
function startAllBackups() {
|
||
if (!confirm('Tüm projelerin yedeklenmesi uzun sürebilir. Devam etmek istediğinizden emin misiniz?')) {
|
||
return;
|
||
}
|
||
|
||
showToast('Toplu yedekleme başlatılıyor...', 'info');
|
||
|
||
fetch('/backup-all-projects/', {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRFToken': getCookie('csrftoken'),
|
||
'Content-Type': 'application/json'
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showToast(data.message, 'success');
|
||
setTimeout(() => location.reload(), 2000);
|
||
} else {
|
||
showToast(data.message || 'Toplu yedekleme başlatılamadı', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Toplu yedekleme hatası:', error);
|
||
showToast('Toplu yedekleme hatası', 'error');
|
||
});
|
||
}
|
||
|
||
// Tek proje yedekleme başlat
|
||
function startBackup() {
|
||
const form = document.getElementById('backupForm');
|
||
const formData = new FormData(form);
|
||
|
||
if (!formData.get('project_id')) {
|
||
showToast('Lütfen bir proje seçin', 'error');
|
||
return;
|
||
}
|
||
|
||
showToast('Yedekleme başlatılıyor...', 'info');
|
||
|
||
fetch('/start-backup/', {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRFToken': getCookie('csrftoken')
|
||
},
|
||
body: formData
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showToast(data.message, 'success');
|
||
bootstrap.Modal.getInstance(document.getElementById('backupModal')).hide();
|
||
setTimeout(() => location.reload(), 2000);
|
||
} else {
|
||
showToast(data.message || 'Yedekleme başlatılamadı', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Yedekleme hatası:', error);
|
||
showToast('Yedekleme hatası', 'error');
|
||
});
|
||
}
|
||
|
||
// Yedeklemeyi tekrar dene
|
||
function retryBackup(projectId) {
|
||
if (!confirm('Bu projenin yedeklenmesini tekrar denemek istediğinizden emin misiniz?')) {
|
||
return;
|
||
}
|
||
|
||
showToast('Yedekleme tekrar deneniyor...', 'info');
|
||
|
||
fetch('/retry-backup/', {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRFToken': getCookie('csrftoken'),
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
project_id: projectId
|
||
})
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showToast(data.message, 'success');
|
||
setTimeout(() => location.reload(), 2000);
|
||
} else {
|
||
showToast(data.message || 'Yedekleme tekrar denenemedi', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Yedekleme tekrar deneme hatası:', error);
|
||
showToast('Yedekleme tekrar deneme hatası', 'error');
|
||
});
|
||
}
|
||
|
||
// Log detayını göster
|
||
function showLogDetail(logOutput, logDate) {
|
||
document.getElementById('logDetailContent').innerHTML = logOutput.replace(/\n/g, '<br>');
|
||
document.querySelector('#logDetailModal .modal-title').innerHTML =
|
||
'<i class="bi bi-file-text me-2"></i>Yedekleme Detayı - ' + logDate;
|
||
|
||
const modal = new bootstrap.Modal(document.getElementById('logDetailModal'));
|
||
modal.show();
|
||
}
|
||
|
||
// Log kaydını sil
|
||
function deleteLog(logId) {
|
||
if (!confirm('Bu log kaydını silmek istediğinizden emin misiniz?')) {
|
||
return;
|
||
}
|
||
|
||
fetch(`/delete-log/${logId}/`, {
|
||
method: 'DELETE',
|
||
headers: {
|
||
'X-CSRFToken': getCookie('csrftoken'),
|
||
'Content-Type': 'application/json'
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showToast('Log kaydı silindi', 'success');
|
||
setTimeout(() => location.reload(), 1000);
|
||
} else {
|
||
showToast(data.message || 'Log kaydı silinemedi', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Log silme hatası:', error);
|
||
showToast('Log silme hatası', 'error');
|
||
});
|
||
}
|
||
</script>
|
||
{% endblock %}
|