555 lines
22 KiB
HTML
555 lines
22 KiB
HTML
{% 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><head></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 %}
|