This commit is contained in:
ilkeral
2025-07-21 13:49:36 +03:00
commit 342f1314c7
57 changed files with 9297 additions and 0 deletions

View File

@ -0,0 +1,554 @@
{% extends 'ssh_manager/base.html' %}
{% block title %}Projeler - Hosting Yönetim Paneli{% endblock %}
{% block content %}
<!-- Projeler Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h3 class="mb-1">
<i class="bi bi-folder-fill me-2"></i>Projeler
</h3>
<small class="text-muted">Tüm hosting projeleri ve yönetim işlemleri</small>
</div>
<div class="d-flex gap-2">
<input type="text" id="projectSearch" class="form-control" style="max-width: 250px;" placeholder="Proje ara...">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addProjectModal">
<i class="bi bi-plus-circle"></i> Yeni Proje
</button>
</div>
</div>
<!-- Projeler Tablosu -->
<div class="table-responsive">
<table class="table table-dark table-striped">
<thead>
<tr>
<th>#</th>
<th>Proje Bilgileri</th>
<th>Klasör & Disk</th>
<th>Site Durumu</th>
<th>Son Yedekleme</th>
<th class="actions">İşlemler</th>
</tr>
</thead>
<tbody>
{% for project in projects %}
<tr>
<td>{{ forloop.counter }}</td>
<td style="line-height: 1.3;">
<div class="fw-bold">{{ project.name }}</div>
{% if project.customer %}
<small class="text-info">
<i class="bi bi-person me-1"></i>{{ project.customer.get_display_name }}
</small><br>
{% endif %}
{% if project.ssh_credential %}
<small class="text-muted">
<i class="bi bi-server me-1"></i>{{ project.ssh_credential.hostname }}
</small>
{% else %}
<small class="text-danger">SSH credential yok</small>
{% endif %}
</td>
<td style="line-height: 1.3;">
<div class="fw-bold">{{ project.folder_name }}</div>
{% if project.disk_usage %}
<small class="text-muted">
<i class="bi bi-hdd me-1"></i>{{ project.disk_usage }}
</small>
{% else %}
<small class="text-muted">-</small>
{% endif %}
</td>
<td>
{% if project.url %}
<div class="d-flex align-items-center mb-1">
{% if project.is_site_active %}
<span class="badge bg-success me-2" title="Site aktif">
<i class="bi bi-check-circle"></i> Aktif
</span>
{% elif project.last_site_check %}
<span class="badge bg-danger me-2" title="Site pasif">
<i class="bi bi-x-circle"></i> Pasif
</span>
{% else %}
<span class="badge bg-secondary me-2" title="Kontrol edilmemiş">
<i class="bi bi-question-circle"></i> Bilinmiyor
</span>
{% endif %}
</div>
<a href="{% if not project.url|slice:':4' == 'http' %}http://{% endif %}{{ project.url }}" target="_blank" class="text-decoration-none small" style="color: #4fc3f7;">
<i class="bi bi-globe me-1"></i>{{ project.url }}
</a>
{% else %}
<span class="text-muted">URL tanımlanmamış</span>
{% endif %}
</td>
<td>
{% if project.last_backup %}
<small>{{ project.last_backup|date:"d.m.Y H:i" }}</small>
{% else %}
<span class="text-warning">Yedek alınmamış</span>
{% endif %}
</td>
<td class="actions">
<i class="action-icon edit bi bi-pencil" onclick="editProject({{ project.id }})" title="Düzenle"></i>
<i class="action-icon delete bi bi-trash" onclick="deleteProject({{ project.id }})" title="Sil"></i>
<i class="action-icon backup bi bi-cloud-arrow-up" onclick="backupProject({{ project.id }})" title="Yedekle"></i>
<i class="action-icon logs bi bi-journal-text" onclick="showLogsByProject({{ project.id }})" title="Loglar"></i>
{% if project.url %}
<i class="action-icon site bi bi-globe-americas" onclick="checkSiteStatus({{ project.id }})" title="Site Kontrol"></i>
<i class="action-icon meta bi bi-key" onclick="showMetaKey({{ project.id }})" title="Meta Key"></i>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center text-muted py-4">
<i class="bi bi-folder" style="font-size: 2rem;"></i>
<div class="mt-2">Henüz proje eklenmemiş</div>
<button class="btn btn-sm btn-outline-primary mt-2" data-bs-toggle="modal" data-bs-target="#addProjectModal">
İlk projeyi ekle
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Proje Ekleme/Düzenleme Modal -->
<div class="modal fade" id="addProjectModal" tabindex="-1" aria-labelledby="addProjectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<form id="projectForm">
<div class="modal-header">
<h5 class="modal-title" id="addProjectModalLabel">Yeni Proje Ekle</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Kapat"></button>
</div>
<div class="modal-body">
<input type="hidden" id="projectId" name="projectId">
<!-- Proje Bilgileri -->
<div class="mb-4">
<h6 class="text-muted mb-3">
<i class="bi bi-folder me-1"></i>Proje Bilgileri
</h6>
<div class="row g-3">
<div class="col-md-6">
<label for="name" class="form-label">Proje Adı *</label>
<input type="text" class="form-control" id="name" name="name" required placeholder="Proje adı">
</div>
<div class="col-md-6">
<label for="folder_name" class="form-label">Klasör Adı *</label>
<input type="text" class="form-control" id="folder_name" name="folder_name" required placeholder="Sunucudaki klasör adı">
</div>
<div class="col-md-6">
<label for="customer" class="form-label">Müşteri</label>
<select class="form-select" id="customer" name="customer">
<option value="">Müşteri Seçiniz (Opsiyonel)</option>
{% for customer in customers %}
<option value="{{ customer.id }}">{{ customer.get_display_name }} ({{ customer.get_customer_type_display }})</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label for="ssh_credential" class="form-label">Host *</label>
<select class="form-select" id="ssh_credential" name="ssh_credential" required>
<option value="">Host Seçiniz</option>
{% for host in ssh_credentials %}
<option value="{{ host.id }}">{{ host.name|default:host.hostname }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<!-- Site Bilgileri -->
<div class="mb-3">
<h6 class="text-muted mb-3">
<i class="bi bi-globe me-1"></i>Site Bilgileri
</h6>
<div class="row g-3">
<div class="col-12">
<label for="url" class="form-label">Site URL'i</label>
<input type="url" class="form-control" id="url" name="url" placeholder="https://example.com">
<small class="text-muted">Site aktiflik kontrolü için gerekli</small>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Kapat</button>
<button type="submit" class="btn btn-primary">Kaydet</button>
</div>
</form>
</div>
</div>
</div>
<!-- Log Modal -->
<div class="modal fade" id="logsModal" tabindex="-1" aria-labelledby="logsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="logsModalLabel">
<i class="bi bi-journal-text me-2"></i>Proje Logları
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Kapat"></button>
</div>
<div class="modal-body" id="logsContent">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Yükleniyor...</span>
</div>
<p class="mt-2">Loglar yükleniyor...</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="clearLogsBtn" onclick="clearProjectLogs()" style="display: none;">
<i class="bi bi-trash"></i> Logları Temizle
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Kapat</button>
</div>
</div>
</div>
</div>
<!-- Meta Key Modal -->
<div class="modal fade" id="metaKeyModal" tabindex="-1" aria-labelledby="metaKeyModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="metaKeyModalLabel">
<i class="bi bi-key me-2"></i>Site Doğrulama Meta Key
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Kapat"></button>
</div>
<div class="modal-body" id="metaKeyContent">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Yükleniyor...</span>
</div>
<p class="mt-2">Meta key bilgileri yükleniyor...</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Kapat</button>
</div>
</div>
</div>
</div>
<script>
// Tüm JavaScript fonksiyonları buradan project_list.html'den kopyalanacak
// Proje düzenleme fonksiyonu
window.editProject = function(id) {
fetch(`/get-project-details/${id}/`)
.then(r => r.json())
.then(data => {
if (data.success) {
document.getElementById('projectId').value = id;
document.getElementById('name').value = data.project.name;
document.getElementById('folder_name').value = data.project.folder_name;
document.getElementById('url').value = data.project.url || '';
document.getElementById('ssh_credential').value = data.project.ssh_credential_id || '';
document.getElementById('customer').value = data.project.customer_id || '';
document.getElementById('addProjectModalLabel').innerText = 'Proje Düzenle';
const modal = new bootstrap.Modal(document.getElementById('addProjectModal'));
modal.show();
} else {
showToast('❌ Proje bilgisi alınamadı!', 'error');
}
})
.catch(error => {
showToast('❌ Proje bilgisi alınırken hata oluştu!', 'error');
});
}
// Proje Sil
window.deleteProject = function(id) {
if (confirm('Projeyi silmek istediğinize emin misiniz?')) {
fetch(`/delete_project/${id}/`, {
method: 'POST',
headers: { 'X-CSRFToken': getCookie('csrftoken') }
})
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('✅ Proje başarıyla silindi', 'success');
setTimeout(() => location.reload(), 1200);
} else {
showToast(`${data.message}`, 'error');
}
});
}
}
// Proje Yedekle
window.backupProject = function(id) {
if (confirm('Projeyi yedeklemek istiyor musunuz?')) {
showToast('🔄 Yedekleme başlatılıyor...', 'info');
fetch(`/backup-project/${id}/`, {
method: 'POST',
headers: { 'X-CSRFToken': getCookie('csrftoken') }
})
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('✅ Yedekleme başarılı', 'success');
setTimeout(() => {
window.showLogsByProject(id);
}, 800);
} else {
showToast(`${data.message}`, 'error');
}
})
.catch(error => {
showToast('❌ Yedekleme hatası!', 'error');
});
}
}
// Log görüntüleme
window.showLogsByProject = function(projectId) {
window.currentProjectId = projectId;
fetch(`/project/${projectId}/backup-logs/`)
.then(r => r.json())
.then(data => {
let html = '';
let hasLogs = false;
if (data.success && data.logs.length > 0) {
hasLogs = true;
html = '<div class="log-list">';
data.logs.forEach(function(log) {
let statusBadge = log.status ?
'<span class="badge bg-success me-2"><i class="bi bi-check-circle"></i> Başarılı</span>' :
'<span class="badge bg-danger me-2"><i class="bi bi-x-circle"></i> Hata</span>';
let typeIcon = '';
if (log.log_type === 'backup') {
typeIcon = '<i class="bi bi-cloud-arrow-up text-warning me-2"></i>';
} else if (log.log_type === 'command') {
typeIcon = '<i class="bi bi-terminal text-info me-2"></i>';
}
html += `
<div class="log-entry mb-3 p-3" style="background: rgba(255,255,255,0.05); border-radius: 8px; border-left: 3px solid ${log.status ? '#28a745' : '#dc3545'};">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>${statusBadge}${typeIcon}<strong>${log.command}</strong></div>
<small class="text-muted">${log.created_at}</small>
</div>
<div class="log-output" style="font-family: monospace; font-size: 0.9em; color: #bdbdbd;">
${log.output.replace(/\n/g, '<br>')}
</div>
</div>
`;
});
html += '</div>';
} else {
html = `
<div class="text-center py-4">
<i class="bi bi-journal-text" style="font-size: 2rem; color: #6c757d;"></i>
<p class="text-muted mt-2">Bu projeye ait log bulunamadı</p>
</div>
`;
}
document.getElementById('logsContent').innerHTML = html;
const clearBtn = document.getElementById('clearLogsBtn');
clearBtn.style.display = hasLogs ? 'inline-block' : 'none';
const logsModal = new bootstrap.Modal(document.getElementById('logsModal'));
logsModal.show();
});
}
// Site durumu kontrol
window.checkSiteStatus = function(projectId) {
showToast('🔄 Site kontrol ediliyor...', 'info');
fetch(`/project/${projectId}/check-site/`, {
method: 'POST',
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
const statusText = data.status ? '✅ Site Aktif' : '❌ Site Pasif';
showToast(statusText, data.status ? 'success' : 'error');
setTimeout(() => location.reload(), 1500);
} else {
showToast(`${data.message}`, 'error');
}
})
.catch(error => {
showToast('❌ Kontrol hatası!', 'error');
});
}
// Meta key göster
window.showMetaKey = function(projectId) {
fetch(`/project/${projectId}/meta-key/`)
.then(response => response.json())
.then(data => {
if (data.success) {
const content = `
<div class="alert alert-info">
<h6><i class="bi bi-info-circle"></i> Kullanım Talimatları:</h6>
<p>Bu meta tag'ı sitenizin <code>&lt;head&gt;</code> bölümüne ekleyin:</p>
</div>
<div class="mb-3">
<label class="form-label"><strong>Meta Key:</strong></label>
<div class="input-group">
<input type="text" class="form-control" value="${data.meta_key}" readonly>
<button class="btn btn-outline-secondary" onclick="copyToClipboard('${data.meta_key}')">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label"><strong>HTML Meta Tag:</strong></label>
<div class="input-group">
<textarea class="form-control" rows="2" readonly>${data.meta_tag}</textarea>
<button class="btn btn-outline-secondary" onclick="copyToClipboard('${data.meta_tag}')">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
<div class="alert alert-warning">
<small><i class="bi bi-exclamation-triangle"></i> Meta tag'ı ekledikten sonra "Site Kontrol" butonuyla doğrulama yapabilirsiniz.</small>
</div>
`;
document.getElementById('metaKeyContent').innerHTML = content;
const modal = new bootstrap.Modal(document.getElementById('metaKeyModal'));
modal.show();
} else {
showToast(`${data.message}`, 'error');
}
})
.catch(error => {
showToast('❌ Meta key alınırken hata oluştu!', 'error');
});
}
// Clipboard'a kopyala
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
showToast('📋 Panoya kopyalandı!', 'success');
}).catch(err => {
showToast('❌ Kopyalama hatası!', 'error');
});
}
// Log temizleme
function clearProjectLogs() {
if (!window.currentProjectId) {
showToast('Proje ID bulunamadı!', 'error');
return;
}
if (!confirm('Bu projenin tüm loglarını silmek istediğinizden emin misiniz?')) {
return;
}
fetch(`/project/${window.currentProjectId}/clear-logs/`, {
method: 'POST',
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('logsModal'));
if (modal) modal.hide();
showToast(`${data.deleted_count} log kaydı silindi`, 'success');
window.currentProjectId = null;
} else {
showToast(`${data.message || 'Log silme işlemi başarısız!'}`, 'error');
}
})
.catch(error => {
showToast('❌ Log silme sırasında bir hata oluştu!', 'error');
});
}
// Proje Form Submit
document.getElementById('projectForm').addEventListener('submit', function(e) {
e.preventDefault();
const id = document.getElementById('projectId').value;
const url = id ? `/update-project/${id}/` : '/project/create/';
const formData = new FormData(this);
fetch(url, {
method: 'POST',
headers: { 'X-CSRFToken': getCookie('csrftoken') },
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('✅ Proje başarıyla kaydedildi', 'success');
const modal = bootstrap.Modal.getInstance(document.getElementById('addProjectModal'));
if (modal) modal.hide();
setTimeout(() => location.reload(), 1200);
} else {
showToast(`${data.message}`, 'error');
}
});
});
// Proje Arama
document.getElementById('projectSearch').addEventListener('keyup', function() {
const searchTerm = this.value.toLowerCase().trim();
const projectRows = document.querySelectorAll('tbody tr');
if (searchTerm.length < 2) {
projectRows.forEach(row => {
if (row.cells.length > 1) {
row.style.display = '';
}
});
return;
}
projectRows.forEach(row => {
if (row.cells.length > 1) {
const projectInfo = row.cells[1].textContent.toLowerCase();
const folderInfo = row.cells[2].textContent.toLowerCase();
const siteInfo = row.cells[3].textContent.toLowerCase();
if (projectInfo.includes(searchTerm) || folderInfo.includes(searchTerm) || siteInfo.includes(searchTerm)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
}
});
});
// Modal reset
document.getElementById('addProjectModal').addEventListener('hidden.bs.modal', function () {
document.getElementById('projectForm').reset();
document.getElementById('projectId').value = '';
document.getElementById('addProjectModalLabel').textContent = 'Yeni Proje Ekle';
});
</script>
{% endblock %}