1522 lines
52 KiB
HTML
1522 lines
52 KiB
HTML
{% extends 'ssh_manager/base.html' %}
|
||
|
||
{% block title %}Yedeklemeler - Hosting Yönetim Paneli{% endblock %}
|
||
|
||
{% block head_extras %}
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff2" as="font" type="font/woff2" crossorigin>
|
||
<script>
|
||
// Filtreleri gönder
|
||
function submitFilters() {
|
||
const form = document.getElementById('filterForm');
|
||
if (form) {
|
||
const statusFilter = document.getElementById('statusFilter');
|
||
const projectFilter = document.getElementById('projectFilter');
|
||
|
||
// Boş değerler için URL parametresi oluşturma
|
||
if (statusFilter && statusFilter.value === '') {
|
||
statusFilter.disabled = true;
|
||
}
|
||
|
||
if (projectFilter && projectFilter.value === '') {
|
||
projectFilter.disabled = true;
|
||
}
|
||
|
||
form.submit();
|
||
} else {
|
||
console.error('Filtre formu bulunamadı!');
|
||
}
|
||
}
|
||
|
||
// Eski filtreleme fonksiyonu (uyumluluk için)
|
||
function applyFilters() {
|
||
submitFilters();
|
||
}
|
||
|
||
// Backup arama fonksiyonu
|
||
function setupBackupSearch() {
|
||
const backupSearch = document.getElementById('backupSearch');
|
||
if (backupSearch) {
|
||
backupSearch.addEventListener('keyup', function() {
|
||
const searchTerm = this.value.toLowerCase().trim();
|
||
const backupRows = document.querySelectorAll('tbody tr');
|
||
|
||
if (searchTerm.length < 2) {
|
||
backupRows.forEach(row => {
|
||
if (row.cells.length > 1) {
|
||
row.style.display = '';
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
|
||
backupRows.forEach(row => {
|
||
if (row.cells.length > 1) {
|
||
const projectInfo = row.cells[0].textContent.toLowerCase();
|
||
const statusInfo = row.cells[2].textContent.toLowerCase();
|
||
|
||
if (projectInfo.includes(searchTerm) || statusInfo.includes(searchTerm)) {
|
||
row.style.display = '';
|
||
} else {
|
||
row.style.display = 'none';
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// Dosya boyutunu insan tarafından okunabilir formata çeviren fonksiyon
|
||
function formatFileSize(bytes) {
|
||
console.log('formatFileSize çağrıldı, gelen değer:', bytes, 'tip:', typeof bytes);
|
||
|
||
// Geçersiz değerler için kontrol
|
||
if (bytes === 0 || bytes === null || bytes === undefined || bytes === '' || isNaN(bytes)) {
|
||
console.log('Geçersiz değer döndürülüyor');
|
||
return '-';
|
||
}
|
||
|
||
// String değeri sayıya çevirme
|
||
if (typeof bytes === 'string') {
|
||
// Eğer zaten formatlanmış bir değerse (örn: "4.2 MB")
|
||
if (/[KMGTPkgtp]B$/i.test(bytes)) {
|
||
console.log('Önceden formatlanmış değer:', bytes);
|
||
return bytes;
|
||
}
|
||
|
||
// Sadece sayısal karakterleri al
|
||
const numericValue = bytes.replace(/[^0-9.]/g, '');
|
||
if (!numericValue) return '-';
|
||
|
||
bytes = parseFloat(numericValue);
|
||
if (isNaN(bytes)) return '-';
|
||
console.log('String sayıya çevrildi:', bytes);
|
||
}
|
||
|
||
// Byte değerini uygun birime çevir
|
||
bytes = Math.abs(bytes); // Negatif değerleri pozitife çevir
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||
|
||
if (bytes === 0) return '0 Bytes';
|
||
|
||
// Logaritmik hesaplama (her 1024 için bir üst birim)
|
||
try {
|
||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||
|
||
// Birim sınırlarını kontrol et
|
||
if (i >= sizes.length) {
|
||
console.log('Çok büyük değer, son birimi kullan:', sizes[sizes.length-1]);
|
||
return (bytes / Math.pow(1024, sizes.length-1)).toFixed(2) + ' ' + sizes[sizes.length-1];
|
||
}
|
||
|
||
if (i === 0) return bytes + ' ' + sizes[i];
|
||
|
||
const formattedSize = (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
|
||
console.log('Formatlanmış boyut:', formattedSize);
|
||
return formattedSize;
|
||
} catch (e) {
|
||
console.error('Boyut hesaplama hatası:', e);
|
||
return bytes + ' Bytes';
|
||
}
|
||
}
|
||
|
||
// DOM yüklendikten sonra çalıştırılacak
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
console.log('DOM yüklendi, dosya boyutu formatlanacak');
|
||
|
||
// Dosya boyutlarını formatla
|
||
const fileSizeElements = document.querySelectorAll('.file-size');
|
||
console.log('Dosya boyutu formatlanıyor. Bulunan eleman sayısı:', fileSizeElements.length);
|
||
|
||
if (fileSizeElements.length === 0) {
|
||
console.log('Formatlanacak dosya boyutu elementi bulunamadı');
|
||
}
|
||
|
||
fileSizeElements.forEach(element => {
|
||
try {
|
||
const bytesAttr = element.getAttribute('data-bytes');
|
||
console.log('Element data-bytes değeri:', bytesAttr);
|
||
|
||
// Eleman boş içerik içeriyor olabilir - temizle
|
||
if (element.textContent.trim() === '') {
|
||
console.log('Element içeriği boş, data-bytes değeri kullanılacak');
|
||
}
|
||
|
||
// Doğrudan bytes değerini formatla
|
||
element.textContent = formatFileSize(bytesAttr);
|
||
console.log('Element içeriği güncellendi:', element.textContent);
|
||
} catch (e) {
|
||
console.error('Dosya boyutu formatlama hatası:', e);
|
||
}
|
||
});
|
||
|
||
console.log('DOM yüklendi, filtreleme hazırlanıyor...');
|
||
|
||
// Filtre elementlerini kontrol et ve olay dinleyicileri ekle
|
||
const statusFilter = document.getElementById('statusFilter');
|
||
const projectFilter = document.getElementById('projectFilter');
|
||
|
||
if (statusFilter) {
|
||
console.log('Status filtresi bulundu, olay dinleyici ekleniyor');
|
||
statusFilter.addEventListener('change', function() {
|
||
console.log('Status filtresi değişti:', this.value);
|
||
submitFilters();
|
||
});
|
||
} else {
|
||
console.error('statusFilter elementi bulunamadı!');
|
||
}
|
||
|
||
if (projectFilter) {
|
||
console.log('Proje filtresi bulundu, olay dinleyici ekleniyor');
|
||
projectFilter.addEventListener('change', function() {
|
||
console.log('Proje filtresi değişti:', this.value);
|
||
submitFilters();
|
||
});
|
||
} else {
|
||
console.error('projectFilter elementi bulunamadı!');
|
||
}
|
||
|
||
// Arama kutusunu ayarla
|
||
setupBackupSearch();
|
||
|
||
// Toast bildirim fonksiyonu
|
||
window.showToast = function(message, type = 'info') {
|
||
// Toast implementation - base.html'deki toast sistemi kullanılır
|
||
const toastContainer = document.querySelector('.toast-container') || document.body;
|
||
const toastEl = document.createElement('div');
|
||
|
||
// Modern görünüm için animasyon ve stil eklemeleri
|
||
toastEl.className = `toast align-items-center text-white bg-${type === 'error' ? 'danger' : type} border-0 shadow-lg`;
|
||
toastEl.setAttribute('role', 'alert');
|
||
toastEl.setAttribute('aria-live', 'assertive');
|
||
toastEl.setAttribute('aria-atomic', 'true');
|
||
|
||
// Özel stil eklemeleri
|
||
toastEl.style.backdropFilter = 'blur(10px)';
|
||
toastEl.style.WebkitBackdropFilter = 'blur(10px)';
|
||
toastEl.style.opacity = '0.95';
|
||
toastEl.style.borderRadius = '8px';
|
||
|
||
toastEl.innerHTML = `
|
||
<div class="d-flex">
|
||
<div class="toast-body">${message}</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Kapat"></button>
|
||
</div>
|
||
`;
|
||
|
||
toastContainer.appendChild(toastEl);
|
||
|
||
// Toast'ı göster
|
||
const toast = new bootstrap.Toast(toastEl, {
|
||
animation: true,
|
||
autohide: true,
|
||
delay: 4000
|
||
});
|
||
|
||
// Animasyon ekle
|
||
toastEl.style.transform = 'translateY(20px)';
|
||
toastEl.style.transition = 'transform 0.3s ease-out';
|
||
|
||
// Toast'ı göster ve animasyon ekle
|
||
toast.show();
|
||
setTimeout(() => {
|
||
toastEl.style.transform = 'translateY(0)';
|
||
}, 50);
|
||
|
||
// Belirli bir süre sonra toast'ı kaldır
|
||
setTimeout(() => {
|
||
toastEl.style.transform = 'translateY(-20px)';
|
||
toastEl.style.opacity = '0';
|
||
toastEl.style.transition = 'transform 0.3s ease-out, opacity 0.3s ease-out';
|
||
setTimeout(() => toastEl.remove(), 300);
|
||
}, 4000);
|
||
}
|
||
|
||
// Diğer başlangıç işlemleri
|
||
const table = document.querySelector('.table-responsive');
|
||
if (table) {
|
||
table.style.opacity = '0';
|
||
table.style.transform = 'translateY(20px)';
|
||
table.style.transition = 'opacity 0.5s ease-out, transform 0.5s ease-out';
|
||
table.style.transitionDelay = '0.3s';
|
||
|
||
setTimeout(() => {
|
||
table.style.opacity = '1';
|
||
table.style.transform = 'translateY(0)';
|
||
}, 400);
|
||
}
|
||
});
|
||
|
||
// CSRF token alma fonksiyonu
|
||
function getCookie(name) {
|
||
let cookieValue = null;
|
||
if (document.cookie && document.cookie !== '') {
|
||
const cookies = document.cookie.split(';');
|
||
for (let i = 0; i < cookies.length; i++) {
|
||
const cookie = cookies[i].trim();
|
||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return cookieValue;
|
||
}
|
||
</script>
|
||
<style>
|
||
/* Bootstrap Icons font yüklemesi için */
|
||
@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css');
|
||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||
|
||
/* Genel font ve stil iyileştirmeleri */
|
||
body {
|
||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
|
||
h1, h2, h3, h4, h5, h6 {
|
||
font-weight: 600;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
|
||
/* Icon font'ların doğru yüklenmesi için */
|
||
.bi::before {
|
||
font-family: 'bootstrap-icons' !important;
|
||
font-style: normal;
|
||
font-variant: normal;
|
||
text-transform: none;
|
||
line-height: 1;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
/* Icon stilleri */
|
||
.bi {
|
||
display: inline-block;
|
||
font-size: inherit;
|
||
text-rendering: auto;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
/* İcon görünürlüğü için */
|
||
i.bi {
|
||
font-style: normal !important;
|
||
font-weight: normal !important;
|
||
}
|
||
|
||
/* Fallback for icon loading */
|
||
.bi:not([class*="bi-"])::before {
|
||
content: "💾";
|
||
}
|
||
|
||
/* Specific icon fallbacks */
|
||
.bi-cloud-arrow-up-fill::before { content: "☁"; }
|
||
.bi-cloud-arrow-up::before { content: "⬆"; }
|
||
.bi-arrow-clockwise::before { content: "🔄"; }
|
||
.bi-check-circle::before { content: "✅"; }
|
||
.bi-x-circle::before { content: "❌"; }
|
||
.bi-clock::before { content: "⏱"; }
|
||
.bi-clock-history::before { content: "🕒"; }
|
||
.bi-person::before { content: "👤"; }
|
||
.bi-server::before { content: "🖥"; }
|
||
.bi-eye::before { content: "👁"; }
|
||
.bi-stop-circle::before { content: "⏹"; }
|
||
.bi-trash::before { content: "🗑"; }
|
||
.bi-slash-circle::before { content: "⊘"; }
|
||
.bi-check-circle-fill::before { content: "✅"; }
|
||
.bi-x-circle-fill::before { content: "❌"; }
|
||
.bi-exclamation-triangle::before { content: "⚠"; }
|
||
.bi-info-circle::before { content: "ℹ"; }
|
||
|
||
/* Icon color fixes - Genel renk düzenlemeleri */
|
||
.text-info .bi,
|
||
.text-info i {
|
||
color: #0dcaf0 !important;
|
||
}
|
||
|
||
.text-success .bi,
|
||
.text-success i {
|
||
color: #198754 !important;
|
||
}
|
||
|
||
.text-danger .bi,
|
||
.text-danger i {
|
||
color: #dc3545 !important;
|
||
}
|
||
|
||
.text-warning .bi,
|
||
.text-warning i {
|
||
color: #ffc107 !important;
|
||
}
|
||
|
||
.text-light .bi,
|
||
.text-light i {
|
||
color: #f8f9fa !important;
|
||
}
|
||
|
||
.text-muted .bi,
|
||
.text-muted i {
|
||
color: #6c757d !important;
|
||
}
|
||
|
||
/* Specific icon color overrides */
|
||
.bi.text-info {
|
||
color: #0dcaf0 !important;
|
||
}
|
||
|
||
.bi.text-success {
|
||
color: #198754 !important;
|
||
}
|
||
|
||
.bi.text-danger {
|
||
color: #dc3545 !important;
|
||
}
|
||
|
||
.bi.text-warning {
|
||
color: #ffc107 !important;
|
||
}
|
||
|
||
/* Header ve buton ikonları için özel renk düzenlemeleri */
|
||
.btn .bi {
|
||
color: inherit !important;
|
||
font-style: normal !important;
|
||
}
|
||
|
||
.btn-success .bi {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.btn-info .bi {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.btn-primary .bi {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.btn-danger .bi {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.btn-warning .bi {
|
||
color: #000000 !important;
|
||
}
|
||
|
||
/* Başlık ikonları için özel renkler */
|
||
h3 .bi.text-info,
|
||
.text-light .bi.text-info {
|
||
color: #0dcaf0 !important;
|
||
font-style: normal !important;
|
||
}
|
||
|
||
/* Kart ikonları için büyük boyut ve renkler */
|
||
.card .bi[style*="font-size: 1.5rem"] {
|
||
font-style: normal !important;
|
||
}
|
||
|
||
/* Badge içindeki ikonlar için özel renkler */
|
||
.badge .bi {
|
||
color: inherit !important;
|
||
font-size: 0.875em;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.badge.bg-success .bi {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.badge.bg-danger .bi {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.badge.bg-warning .bi {
|
||
color: #000000 !important;
|
||
}
|
||
|
||
.badge.bg-secondary .bi {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.badge.bg-info .bi {
|
||
color: #000000 !important;
|
||
}
|
||
|
||
.badge.bg-primary .bi {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
/* Tablo içindeki küçük ikonlar için renk düzeltmeleri */
|
||
.table td small .bi {
|
||
color: inherit !important;
|
||
font-size: 0.875em;
|
||
}
|
||
|
||
.table td .text-info small .bi,
|
||
.table td small.text-info .bi {
|
||
color: #0dcaf0 !important;
|
||
}
|
||
|
||
.table td .text-muted small .bi,
|
||
.table td small.text-muted .bi {
|
||
color: #6c757d !important;
|
||
}
|
||
|
||
/* Dark mode için genel stiller */
|
||
body {
|
||
background-color: #121212;
|
||
color: #ffffff;
|
||
}
|
||
|
||
/* Dark mode için form kontrolleri */
|
||
.form-control:focus, .form-select:focus {
|
||
background-color: #1e1e1e !important;
|
||
border-color: #495057;
|
||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||
color: #fff !important;
|
||
}
|
||
|
||
.form-control::placeholder {
|
||
color: #6c757d !important;
|
||
}
|
||
|
||
.form-control.bg-dark, .form-select.bg-dark {
|
||
background-color: #1e1e1e !important;
|
||
border-color: #495057;
|
||
color: #fff !important;
|
||
}
|
||
|
||
/* Dark mode için tablo stilleri */
|
||
.table-dark {
|
||
border-color: #495057;
|
||
}
|
||
|
||
.table-dark thead th {
|
||
background-color: #1e1e1e;
|
||
border-color: #495057;
|
||
}
|
||
|
||
.table-dark tbody tr {
|
||
transition: background-color 0.3s ease;
|
||
}
|
||
|
||
.table-dark tbody tr:hover {
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
}
|
||
|
||
/* Dark mode için card stilleri */
|
||
.card {
|
||
background-color: #1e1e1e;
|
||
border: 1px solid #495057;
|
||
border-radius: 8px;
|
||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||
}
|
||
|
||
.card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
/* Dark mode için icon box */
|
||
.icon-box {
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
/* Badge stilleri */
|
||
.badge {
|
||
font-size: 0.75rem;
|
||
padding: 0.35em 0.65em;
|
||
}
|
||
|
||
/* Select option dark mode */
|
||
select.form-select option {
|
||
background-color: #1e1e1e;
|
||
color: #fff;
|
||
}
|
||
|
||
/* Modal dark mode iyileştirmeleri */
|
||
.modal-content.bg-dark {
|
||
border: 1px solid #495057;
|
||
}
|
||
|
||
.modal-header.bg-dark {
|
||
border-bottom: 1px solid #495057;
|
||
}
|
||
|
||
.modal-footer.bg-dark {
|
||
border-top: 1px solid #495057;
|
||
}
|
||
|
||
/* Toast dark mode */
|
||
.toast.bg-success, .toast.bg-danger, .toast.bg-info, .toast.bg-warning {
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
/* Buton stilleri */
|
||
.btn {
|
||
border-radius: 4px;
|
||
transition: transform 0.2s ease;
|
||
}
|
||
|
||
.btn:hover {
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
/* Action ikon spesifik renkleri */
|
||
.action-icon.edit {
|
||
color: #0d6efd !important;
|
||
}
|
||
|
||
.action-icon.edit:hover {
|
||
color: #0a58ca !important;
|
||
background: rgba(13, 110, 253, 0.15);
|
||
}
|
||
|
||
.action-icon.delete {
|
||
color: #dc3545 !important;
|
||
}
|
||
|
||
.action-icon.delete:hover {
|
||
color: #b02a37 !important;
|
||
background: rgba(220, 53, 69, 0.15);
|
||
}
|
||
|
||
.action-icon.details {
|
||
color: #17a2b8 !important;
|
||
}
|
||
|
||
.action-icon.details:hover {
|
||
color: #138496 !important;
|
||
background: rgba(23, 162, 184, 0.15);
|
||
}
|
||
|
||
.action-icon.stop {
|
||
color: #fd7e14 !important;
|
||
}
|
||
|
||
.action-icon.stop:hover {
|
||
color: #e8680b !important;
|
||
background: rgba(253, 126, 20, 0.15);
|
||
}
|
||
|
||
.action-icon.retry {
|
||
color: #28a745 !important;
|
||
}
|
||
|
||
.action-icon.retry:hover {
|
||
color: #1e7e34 !important;
|
||
background: rgba(40, 167, 69, 0.15);
|
||
}
|
||
|
||
/* Fallback renkleri için özel kurallar */
|
||
.action-icon:not(.bi-eye):not(.bi-stop-circle):not(.bi-arrow-clockwise):not(.bi-trash) {
|
||
color: #6c757d !important;
|
||
}
|
||
|
||
/* İkon hover renklerini zorla uygula */
|
||
.actions .action-icon.bi-eye {
|
||
color: #17a2b8 !important;
|
||
}
|
||
|
||
.actions .action-icon.bi-stop-circle {
|
||
color: #fd7e14 !important;
|
||
}
|
||
|
||
.actions .action-icon.bi-arrow-clockwise {
|
||
color: #28a745 !important;
|
||
}
|
||
|
||
.actions .action-icon.bi-trash {
|
||
color: #dc3545 !important;
|
||
}
|
||
|
||
/* Modern UI için stil düzenlemeleri */
|
||
:root {
|
||
--dark-bg: #121212;
|
||
--dark-surface: #1e1e1e;
|
||
--dark-border: #333333;
|
||
--primary: #3b82f6;
|
||
--success: #22c55e;
|
||
--warning: #f59e0b;
|
||
--danger: #ef4444;
|
||
--info: #06b6d4;
|
||
}
|
||
|
||
body {
|
||
background-color: var(--dark-bg);
|
||
color: #ffffff;
|
||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
line-height: 1.5;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
|
||
/* Kart stilleri */
|
||
.card {
|
||
background: linear-gradient(145deg, rgba(30, 30, 30, 0.9), rgba(25, 25, 25, 0.8));
|
||
border: 1px solid var(--dark-border) !important;
|
||
border-radius: 12px !important;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.05);
|
||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.card:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.card-header {
|
||
border-bottom: 1px solid var(--dark-border) !important;
|
||
padding: 1rem 1.25rem;
|
||
background: rgba(30, 30, 30, 0.5) !important;
|
||
border-top-left-radius: 11px !important;
|
||
border-top-right-radius: 11px !important;
|
||
}
|
||
|
||
/* Tablo stilleri */
|
||
.table {
|
||
margin-bottom: 0;
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
}
|
||
|
||
.table-dark {
|
||
--bs-table-bg: transparent;
|
||
color: #e2e8f0;
|
||
}
|
||
|
||
.table-hover tbody tr:hover {
|
||
background-color: rgba(255, 255, 255, 0.05) !important;
|
||
}
|
||
|
||
.table thead th {
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
font-size: 0.75rem;
|
||
letter-spacing: 0.5px;
|
||
padding: 0.75rem 1rem;
|
||
border-bottom: 1px solid var(--dark-border);
|
||
}
|
||
|
||
.table tbody td {
|
||
padding: 1rem;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.table tbody tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
/* Badge stilleri */
|
||
.badge {
|
||
font-weight: 500;
|
||
padding: 0.35em 0.65em;
|
||
font-size: 0.75rem;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
line-height: 1;
|
||
}
|
||
|
||
.badge.rounded-pill {
|
||
padding-left: 0.8em;
|
||
padding-right: 0.8em;
|
||
}
|
||
|
||
.badge .bi {
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 0.4em 0.75em;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Input ve form stilleri */
|
||
.form-control, .form-select {
|
||
background-color: rgba(30, 30, 30, 0.9) !important;
|
||
border: 1px solid var(--dark-border);
|
||
color: #fff !important;
|
||
border-radius: 8px;
|
||
padding: 0.6rem 1rem;
|
||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||
}
|
||
|
||
.form-control:focus, .form-select:focus {
|
||
background-color: rgba(35, 35, 35, 0.95) !important;
|
||
border-color: rgba(59, 130, 246, 0.5);
|
||
box-shadow: 0 0 0 0.25rem rgba(59, 130, 246, 0.2);
|
||
}
|
||
|
||
.input-group-text {
|
||
background-color: rgba(30, 30, 30, 0.9) !important;
|
||
border: 1px solid var(--dark-border);
|
||
color: #6c757d;
|
||
border-radius: 8px;
|
||
padding: 0.6rem 1rem;
|
||
}
|
||
|
||
.form-label {
|
||
font-weight: 500;
|
||
margin-bottom: 0.5rem;
|
||
color: #a3a3a3;
|
||
}
|
||
|
||
/* Buton stilleri */
|
||
.btn {
|
||
font-weight: 500;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 8px;
|
||
transition: all 0.2s ease-in-out;
|
||
}
|
||
|
||
.btn:active {
|
||
transform: scale(0.97);
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: var(--primary);
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background-color: #2563eb;
|
||
border-color: #2563eb;
|
||
}
|
||
|
||
.btn-success {
|
||
background-color: var(--success);
|
||
border-color: var(--success);
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background-color: #16a34a;
|
||
border-color: #16a34a;
|
||
}
|
||
|
||
.btn-outline-info {
|
||
color: var(--info);
|
||
border-color: var(--info);
|
||
}
|
||
|
||
.btn-outline-info:hover {
|
||
background-color: var(--info);
|
||
color: #000;
|
||
}
|
||
|
||
.btn-outline-danger {
|
||
color: var(--danger);
|
||
border-color: var(--danger);
|
||
}
|
||
|
||
.btn-outline-danger:hover {
|
||
background-color: var(--danger);
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-outline-warning {
|
||
color: var(--warning);
|
||
border-color: var(--warning);
|
||
}
|
||
|
||
.btn-outline-warning:hover {
|
||
background-color: var(--warning);
|
||
color: #000;
|
||
}
|
||
|
||
.btn-with-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.btn-icon-only {
|
||
width: 40px;
|
||
height: 38px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* İstatistik kartları için özel stiller */
|
||
.stats-card {
|
||
overflow: hidden;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.stats-icon {
|
||
box-shadow: 0 0 20px rgba(var(--bs-info-rgb), 0.15);
|
||
}
|
||
|
||
/* Animasyonlar */
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
100% { transform: scale(1); }
|
||
}
|
||
|
||
.animate-fadeIn {
|
||
animation: fadeIn 0.5s ease forwards;
|
||
}
|
||
|
||
/* Spinner animasyonu */
|
||
.spinner-border {
|
||
animation-duration: 1s;
|
||
}
|
||
|
||
/* Scrollbar stilleri */
|
||
::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: rgba(30, 30, 30, 0.5);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: rgba(100, 100, 100, 0.5);
|
||
border-radius: 4px;
|
||
transition: background 0.2s ease;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: rgba(120, 120, 120, 0.7);
|
||
}
|
||
|
||
/* Modal stilleri */
|
||
.modal-content {
|
||
background: linear-gradient(145deg, rgba(30, 30, 30, 0.95), rgba(25, 25, 25, 0.9));
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid var(--dark-border);
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.modal-header, .modal-footer {
|
||
border-color: var(--dark-border);
|
||
}
|
||
|
||
/* Detay içerik stilleri */
|
||
.detail-item .detail-label {
|
||
font-size: 0.75rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
color: #a3a3a3;
|
||
}
|
||
|
||
.detail-item .detail-value {
|
||
font-weight: 500;
|
||
}
|
||
|
||
pre.error-message {
|
||
font-size: 0.875rem;
|
||
white-space: pre-wrap;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
/* Alert stilleri */
|
||
.alert {
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.empty-state {
|
||
animation: pulse 2s infinite ease-in-out;
|
||
}
|
||
|
||
/* Responsive iyileştirmeler */
|
||
@media (max-width: 768px) {
|
||
.table {
|
||
display: block;
|
||
overflow-x: auto;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.stats-card .d-flex {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.stats-icon {
|
||
margin-bottom: 1rem;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<!-- Filtreler -->
|
||
<div class="card bg-dark border-secondary mb-4 shadow-sm">
|
||
<div class="card-body">
|
||
<form id="filterForm" action="" method="get">
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<label for="statusFilter" class="form-label text-light small mb-1">Durum Filtresi</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-dark border-secondary text-light">
|
||
<i class="bi bi-funnel"></i>
|
||
</span>
|
||
<select class="form-select bg-dark text-light border-secondary" id="statusFilter" name="status">
|
||
<option value="">Tüm Durumlar</option>
|
||
<option value="completed" {% if request.GET.status == 'completed' %}selected{% endif %}>Tamamlandı</option>
|
||
<option value="failed" {% if request.GET.status == 'failed' %}selected{% endif %}>Başarısız</option>
|
||
<option value="running" {% if request.GET.status == 'running' %}selected{% endif %}>Devam Ediyor</option>
|
||
<option value="cancelled" {% if request.GET.status == 'cancelled' %}selected{% endif %}>İptal Edildi</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label for="projectFilter" class="form-label text-light small mb-1">Proje Filtresi</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-dark border-secondary text-light">
|
||
<i class="bi bi-folder"></i>
|
||
</span>
|
||
<select class="form-select bg-dark text-light border-secondary" id="projectFilter" name="project">
|
||
<option value="">Tüm Projeler</option>
|
||
{% for project in projects %}
|
||
<option value="{{ project.id }}" {% if request.GET.project == project.id|stringformat:"s" %}selected{% endif %}>
|
||
{{ project.name }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- İstatistik Kartları -->
|
||
<div class="row mb-4 g-3">
|
||
<div class="col-sm-6 col-xl-3">
|
||
<div class="card bg-dark border-secondary shadow-sm h-100 stats-card">
|
||
<div class="card-body p-0">
|
||
<div class="d-flex h-100">
|
||
<div class="stats-icon bg-success bg-opacity-15 d-flex align-items-center justify-content-center" style="min-width: 80px;">
|
||
<i class="bi bi-check-circle-fill text-success" style="font-size: 2rem;"></i>
|
||
</div>
|
||
<div class="stats-details p-3">
|
||
<h2 class="mb-1 text-success" id="successCount">{% if backup_stats.completed %}{{ backup_stats.completed }}{% else %}0{% endif %}</h2>
|
||
<p class="text-muted mb-0 small">Başarılı Yedekleme</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6 col-xl-3">
|
||
<div class="card bg-dark border-secondary shadow-sm h-100 stats-card">
|
||
<div class="card-body p-0">
|
||
<div class="d-flex h-100">
|
||
<div class="stats-icon bg-danger bg-opacity-15 d-flex align-items-center justify-content-center" style="min-width: 80px;">
|
||
<i class="bi bi-x-circle-fill text-danger" style="font-size: 2rem;"></i>
|
||
</div>
|
||
<div class="stats-details p-3">
|
||
<h2 class="mb-1 text-danger" id="failedCount">{% if backup_stats.failed %}{{ backup_stats.failed }}{% else %}0{% endif %}</h2>
|
||
<p class="text-muted mb-0 small">Başarısız Yedekleme</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6 col-xl-3">
|
||
<div class="card bg-dark border-secondary shadow-sm h-100 stats-card">
|
||
<div class="card-body p-0">
|
||
<div class="d-flex h-100">
|
||
<div class="stats-icon bg-warning bg-opacity-15 d-flex align-items-center justify-content-center" style="min-width: 80px;">
|
||
<i class="bi bi-clock text-warning" style="font-size: 2rem;"></i>
|
||
</div>
|
||
<div class="stats-details p-3">
|
||
<h2 class="mb-1 text-warning" id="runningCount">{% if backup_stats.running %}{{ backup_stats.running }}{% else %}0{% endif %}</h2>
|
||
<p class="text-muted mb-0 small">Devam Eden</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6 col-xl-3">
|
||
<div class="card bg-dark border-secondary shadow-sm h-100 stats-card">
|
||
<div class="card-body p-0">
|
||
<div class="d-flex h-100">
|
||
<div class="stats-icon bg-info bg-opacity-15 d-flex align-items-center justify-content-center" style="min-width: 80px;">
|
||
<i class="bi bi-cloud-arrow-up-fill text-info" style="font-size: 2rem;"></i>
|
||
</div>
|
||
<div class="stats-details p-3">
|
||
<h2 class="mb-1 text-info" id="totalCount">{% if backup_stats.total %}{{ backup_stats.total }}{% else %}0{% endif %}</h2>
|
||
<p class="text-muted mb-0 small">Toplam Yedekleme</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Yedekleme Kayıtları -->
|
||
<div class="card bg-dark border-secondary shadow-sm mb-4">
|
||
<div class="card-header bg-dark border-secondary d-flex align-items-center">
|
||
<div class="icon-box bg-primary bg-opacity-15 me-3" style="width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center;">
|
||
<i class="bi bi-clock-history text-primary" style="font-size: 1.25rem;"></i>
|
||
</div>
|
||
<h5 class="card-title mb-0 text-light">Yedekleme Kayıtları</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if backups %}
|
||
<div class="table-responsive">
|
||
<table class="table table-dark table-hover table-striped align-middle mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th class="text-light">Proje</th>
|
||
<th class="text-light">Tip</th>
|
||
<th class="text-light">Durum</th>
|
||
<th class="text-light">Başlangıç</th>
|
||
<th class="text-light">Bitiş</th>
|
||
<th class="text-light">Süre</th>
|
||
<th class="text-light">Boyut</th>
|
||
<th class="actions text-light text-end">İşlemler</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for backup in backups %}
|
||
<tr>
|
||
<td style="line-height: 1.3;">
|
||
<div class="d-flex align-items-center">
|
||
<div class="project-icon me-3 d-none d-md-flex" style="width: 40px; height: 40px; border-radius: 8px; background-color: rgba(13, 110, 253, 0.15); display: flex; align-items: center; justify-content: center;">
|
||
<i class="bi bi-folder2 text-primary"></i>
|
||
</div>
|
||
<div>
|
||
<div class="fw-bold text-light">{{ backup.project.name }}</div>
|
||
{% if backup.project.customer %}
|
||
<small class="text-info">
|
||
<i class="bi bi-person me-1"></i>{{ backup.project.customer.get_display_name }}
|
||
</small>
|
||
{% endif %}
|
||
<div class="d-block d-md-none mt-1">
|
||
<small class="text-muted">
|
||
<i class="bi bi-server me-1"></i>{{ backup.project.ssh_credential.hostname }}
|
||
</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
{% if backup.backup_type == 'manual' %}
|
||
<span class="badge bg-primary rounded-pill">Manuel</span>
|
||
{% elif backup.backup_type == 'auto' %}
|
||
<span class="badge bg-info rounded-pill">Otomatik</span>
|
||
{% else %}
|
||
<span class="badge bg-warning rounded-pill">Zamanlanmış</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if backup.status == 'completed' %}
|
||
<span class="badge bg-success status-badge">
|
||
<i class="bi bi-check-circle me-1"></i>Tamamlandı
|
||
</span>
|
||
{% elif backup.status == 'failed' %}
|
||
<span class="badge bg-danger status-badge">
|
||
<i class="bi bi-x-circle me-1"></i>Başarısız
|
||
</span>
|
||
{% elif backup.status == 'running' %}
|
||
<span class="badge bg-warning status-badge">
|
||
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||
Devam Ediyor
|
||
</span>
|
||
{% else %}
|
||
<span class="badge bg-secondary status-badge">
|
||
<i class="bi bi-slash-circle me-1"></i>İptal Edildi
|
||
</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<small class="text-light">{{ backup.start_time|date:"d.m.Y H:i:s" }}</small>
|
||
</td>
|
||
<td>
|
||
{% if backup.end_time %}
|
||
<small class="text-light">{{ backup.end_time|date:"d.m.Y H:i:s" }}</small>
|
||
{% else %}
|
||
<span class="text-muted">-</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">{{ backup.duration }}</small>
|
||
</td>
|
||
<td>
|
||
{% if backup.file_size %}
|
||
<span class="badge bg-info bg-opacity-25 text-info file-size" data-bytes="{{ backup.file_size }}">
|
||
{{ backup.file_size }} <!-- JS ile değiştirilecek, ancak JS çalışmazsa ham veri görünsün -->
|
||
</span>
|
||
{% else %}
|
||
<span class="text-muted">-</span>
|
||
{% endif %}
|
||
</td>
|
||
<td class="actions text-end">
|
||
<div class="btn-group">
|
||
<button class="btn btn-sm btn-outline-info" onclick="showBackupDetails({{ backup.id }})" title="Detaylar">
|
||
<i class="bi bi-eye"></i>
|
||
</button>
|
||
|
||
{% if backup.status == 'running' %}
|
||
<button class="btn btn-sm btn-outline-warning" onclick="cancelBackup({{ backup.id }})" title="İptal Et">
|
||
<i class="bi bi-stop-circle"></i>
|
||
</button>
|
||
{% elif backup.status == 'failed' %}
|
||
<button class="btn btn-sm btn-outline-success" onclick="retryBackup({{ backup.project.id }})" title="Tekrar Dene">
|
||
<i class="bi bi-arrow-clockwise"></i>
|
||
</button>
|
||
{% endif %}
|
||
|
||
<button class="btn btn-sm btn-outline-danger" onclick="deleteBackupRecord({{ backup.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">
|
||
<div class="empty-state mb-3">
|
||
<div class="icon-box bg-secondary bg-opacity-15 mx-auto" style="width: 80px; height: 80px; border-radius: 40px; display: flex; align-items: center; justify-content: center;">
|
||
<i class="bi bi-cloud-arrow-up-fill text-secondary" style="font-size: 2.5rem;"></i>
|
||
</div>
|
||
</div>
|
||
<h5 class="mt-3 text-light">Henüz yedekleme kaydı bulunmuyor</h5>
|
||
<p class="text-muted mb-4">İlk yedekleme işleminizi başlatmak için yukarıdaki butonları kullanın.</p>
|
||
<button class="btn btn-primary" onclick="startAllBackups()">
|
||
<i class="bi bi-cloud-arrow-up me-2"></i>Toplu Yedekleme Başlat
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Backup Detay Modal -->
|
||
<div class="modal fade" id="backupDetailModal" tabindex="-1">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content bg-dark border-secondary">
|
||
<div class="modal-header bg-dark border-secondary">
|
||
<div class="d-flex align-items-center">
|
||
<div class="icon-box bg-info bg-opacity-15 me-3" style="width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center;">
|
||
<i class="bi bi-info-circle text-info" style="font-size: 1.25rem;"></i>
|
||
</div>
|
||
<h5 class="modal-title text-light mb-0">Yedekleme Detayları</h5>
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Kapat"></button>
|
||
</div>
|
||
<div class="modal-body bg-dark text-light" id="backupDetailContent">
|
||
<!-- İçerik JavaScript ile doldurulacak -->
|
||
<div class="text-center py-3">
|
||
<div class="spinner-border text-info" role="status">
|
||
<span class="visually-hidden">Yükleniyor...</span>
|
||
</div>
|
||
<p class="mt-2 text-muted">Detaylar yükleniyor...</p>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer bg-dark border-secondary">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Kapat</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Edit Backup modal -->
|
||
<div class="modal fade" id="editBackupModal" tabindex="-1" aria-labelledby="editBackupModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content bg-dark border-secondary">
|
||
<div class="modal-header bg-dark border-secondary">
|
||
<h5 class="modal-title text-light" id="editBackupModalLabel">Yedekleme Ayarlarını Düzenle</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Kapat"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="editBackupForm">
|
||
<input type="hidden" id="editBackupId" name="backup_id">
|
||
<div class="mb-3">
|
||
<label for="editProjectName" class="form-label text-light">Proje Adı</label>
|
||
<input type="text" class="form-control bg-dark text-light border-secondary" id="editProjectName" name="project_name" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="editProjectFolder" class="form-label text-light">Klasör Yolu</label>
|
||
<input type="text" class="form-control bg-dark text-light border-secondary" id="editProjectFolder" name="project_folder" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="editHostName" class="form-label text-light">Host Adı</label>
|
||
<input type="text" class="form-control bg-dark text-light border-secondary" id="editHostName" name="host_name" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="editBackupType" class="form-label text-light">Yedekleme Tipi</label>
|
||
<select class="form-select bg-dark text-light border-secondary" id="editBackupType" name="backup_type" required>
|
||
<option value="full">Tam Yedekleme</option>
|
||
<option value="incremental">Artımlı Yedekleme</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="editStatus" class="form-label text-light">Durum</label>
|
||
<select class="form-select bg-dark text-light border-secondary" id="editStatus" name="status" required>
|
||
<option value="running">Devam Ediyor</option>
|
||
<option value="completed">Tamamlandı</option>
|
||
<option value="failed">Başarısız</option>
|
||
<option value="cancelled">İptal Edildi</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label for="editNotes" class="form-label text-light">Notlar</label>
|
||
<textarea class="form-control bg-dark text-light border-secondary" id="editNotes" name="notes" rows="3"></textarea>
|
||
</div>
|
||
<div class="text-end">
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="bi bi-save"></i> Kaydet
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
// Sayfayı yenile
|
||
function refreshPage() {
|
||
location.reload();
|
||
}
|
||
|
||
// Tüm projeleri yedekle
|
||
function startAllBackups() {
|
||
if (confirm('Tüm projeleri yedeklemek istediğinize emin misiniz? Bu işlem uzun sürebilir.')) {
|
||
showToast('<i class="bi bi-arrow-clockwise me-2"></i>Toplu yedekleme başlatılıyor...', 'info');
|
||
|
||
fetch('/backup-all-projects/', {
|
||
method: 'POST',
|
||
headers: { 'X-CSRFToken': getCookie('csrftoken') }
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showToast('<i class="bi bi-check-circle-fill me-2"></i>Toplu yedekleme başlatıldı', 'success');
|
||
setTimeout(() => location.reload(), 2000);
|
||
} else {
|
||
showToast(`<i class="bi bi-x-circle-fill me-2"></i>${data.message}`, 'danger');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
showToast('<i class="bi bi-x-circle-fill me-2"></i>Toplu yedekleme başlatılamadı!', 'danger');
|
||
});
|
||
}
|
||
}
|
||
|
||
// Backup detaylarını göster
|
||
function showBackupDetails(backupId) {
|
||
// Modal gösterme öncesi loading state
|
||
document.getElementById('backupDetailContent').innerHTML = `
|
||
<div class="text-center py-4">
|
||
<div class="spinner-border text-info" role="status">
|
||
<span class="visually-hidden">Yükleniyor...</span>
|
||
</div>
|
||
<p class="mt-3 text-muted">Detaylar yükleniyor...</p>
|
||
</div>
|
||
`;
|
||
|
||
// Modal'ı göster
|
||
new bootstrap.Modal(document.getElementById('backupDetailModal')).show();
|
||
|
||
// Veri çek
|
||
fetch(`/backup-details/${backupId}/`)
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
const backup = data.backup;
|
||
|
||
// Durum badge'ini hazırla
|
||
let statusBadge = '';
|
||
switch(backup.status) {
|
||
case 'completed':
|
||
statusBadge = `<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Tamamlandı</span>`;
|
||
break;
|
||
case 'failed':
|
||
statusBadge = `<span class="badge bg-danger"><i class="bi bi-x-circle me-1"></i>Başarısız</span>`;
|
||
break;
|
||
case 'running':
|
||
statusBadge = `<span class="badge bg-warning"><i class="bi bi-clock me-1"></i>Devam Ediyor</span>`;
|
||
break;
|
||
default:
|
||
statusBadge = `<span class="badge bg-secondary"><i class="bi bi-slash-circle me-1"></i>İptal Edildi</span>`;
|
||
}
|
||
|
||
// Tip badge'ini hazırla
|
||
let typeBadge = '';
|
||
switch(backup.backup_type) {
|
||
case 'manual':
|
||
typeBadge = `<span class="badge bg-primary rounded-pill">Manuel</span>`;
|
||
break;
|
||
case 'auto':
|
||
typeBadge = `<span class="badge bg-info rounded-pill">Otomatik</span>`;
|
||
break;
|
||
default:
|
||
typeBadge = `<span class="badge bg-warning rounded-pill">Zamanlanmış</span>`;
|
||
}
|
||
|
||
// İçeriği hazırla
|
||
const content = `
|
||
<div class="card bg-dark border-secondary mb-3">
|
||
<div class="card-header bg-dark border-secondary">
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center">
|
||
<h6 class="mb-0 text-light"><i class="bi bi-folder me-2 text-primary"></i>${backup.project_name}</h6>
|
||
<div class="d-flex gap-2">
|
||
${typeBadge}
|
||
${statusBadge}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<div class="detail-item mb-3">
|
||
<div class="detail-label text-muted small mb-1">Proje Klasörü</div>
|
||
<div class="detail-value text-light">${backup.project_folder || '-'}</div>
|
||
</div>
|
||
<div class="detail-item mb-3">
|
||
<div class="detail-label text-muted small mb-1">Host</div>
|
||
<div class="detail-value text-light">${backup.host_name || '-'}</div>
|
||
</div>
|
||
<div class="detail-item">
|
||
<div class="detail-label text-muted small mb-1">Dosya Yolu</div>
|
||
<div class="detail-value text-light">${backup.file_path || '-'}</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="detail-item mb-3">
|
||
<div class="detail-label text-muted small mb-1">Başlangıç Zamanı</div>
|
||
<div class="detail-value text-light">${backup.start_time || '-'}</div>
|
||
</div>
|
||
<div class="detail-item mb-3">
|
||
<div class="detail-label text-muted small mb-1">Bitiş Zamanı</div>
|
||
<div class="detail-value text-light">${backup.end_time || '-'}</div>
|
||
</div>
|
||
<div class="detail-item">
|
||
<div class="detail-label text-muted small mb-1">Dosya Boyutu</div>
|
||
<div class="detail-value text-light file-size" data-bytes="${backup.file_size || 0}">${backup.file_size ? formatFileSize(backup.file_size) : '-'}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
${backup.error_message ? `
|
||
<div class="alert alert-danger bg-opacity-25 border-danger">
|
||
<div class="d-flex align-items-start">
|
||
<i class="bi bi-exclamation-triangle-fill text-danger me-3 mt-1" style="font-size: 1.25rem;"></i>
|
||
<div>
|
||
<h6 class="text-danger mb-2">Hata Mesajı</h6>
|
||
<pre class="error-message bg-dark p-3 rounded border border-danger">${backup.error_message}</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
|
||
${backup.notes ? `
|
||
<div class="alert alert-info bg-opacity-25 border-info">
|
||
<div class="d-flex align-items-start">
|
||
<i class="bi bi-info-circle-fill text-info me-3 mt-1" style="font-size: 1.25rem;"></i>
|
||
<div>
|
||
<h6 class="text-info mb-2">Notlar</h6>
|
||
<p class="mb-0">${backup.notes}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
`;
|
||
|
||
document.getElementById('backupDetailContent').innerHTML = content;
|
||
} else {
|
||
document.getElementById('backupDetailContent').innerHTML = `
|
||
<div class="alert alert-danger">
|
||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||
Backup detayları alınamadı! Lütfen daha sonra tekrar deneyin.
|
||
</div>
|
||
`;
|
||
showToast('<i class="bi bi-x-circle-fill me-2"></i>Backup detayları alınamadı!', 'danger');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
document.getElementById('backupDetailContent').innerHTML = `
|
||
<div class="alert alert-danger">
|
||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||
Detay bilgisi alınırken bir hata oluştu! Lütfen daha sonra tekrar deneyin.
|
||
</div>
|
||
`;
|
||
showToast('<i class="bi bi-x-circle-fill me-2"></i>Detay bilgisi alınırken hata oluştu!', 'danger');
|
||
});
|
||
}
|
||
|
||
// Backup'ı iptal et
|
||
function cancelBackup(backupId) {
|
||
if (confirm('Yedekleme işlemini iptal etmek istediğinize emin misiniz?')) {
|
||
fetch(`/cancel-backup/${backupId}/`, {
|
||
method: 'POST',
|
||
headers: { 'X-CSRFToken': getCookie('csrftoken') }
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showToast('<i class="bi bi-check-circle-fill me-2"></i>Yedekleme iptal edildi', 'success');
|
||
setTimeout(() => location.reload(), 1500);
|
||
} else {
|
||
showToast(`<i class="bi bi-x-circle-fill me-2"></i>${data.message}`, 'danger');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// Backup'ı tekrar dene
|
||
function retryBackup(projectId) {
|
||
if (confirm('Yedekleme işlemini tekrar denemek istediğinize emin misiniz?')) {
|
||
fetch(`/backup-project/${projectId}/`, {
|
||
method: 'POST',
|
||
headers: { 'X-CSRFToken': getCookie('csrftoken') }
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showToast('<i class="bi bi-check-circle-fill me-2"></i>Yedekleme yeniden başlatıldı', 'success');
|
||
setTimeout(() => location.reload(), 2000);
|
||
} else {
|
||
showToast(`<i class="bi bi-x-circle-fill me-2"></i>${data.message}`, 'danger');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// Backup kaydını sil
|
||
function deleteBackupRecord(backupId) {
|
||
if (confirm('Bu yedekleme kaydını silmek istediğinize emin misiniz?')) {
|
||
fetch(`/delete-backup-record/${backupId}/`, {
|
||
method: 'POST',
|
||
headers: { 'X-CSRFToken': getCookie('csrftoken') }
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showToast('<i class="bi bi-check-circle-fill me-2"></i>Yedekleme kaydı silindi', 'success');
|
||
setTimeout(() => location.reload(), 1500);
|
||
} else {
|
||
showToast(`<i class="bi bi-x-circle-fill me-2"></i>${data.message}`, 'danger');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// Daha önce sayfanın üst kısmında tanımlandı
|
||
</script>
|
||
{% endblock %}
|