Files
hostpanel/templates/ssh_manager/yedeklemeler.html
ilkeral f4ee7a2d0b yeni
2025-08-08 07:24:25 +03:00

1522 lines
52 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

{% 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 %}