class PKIManager { constructor() { this.apiBase = '/api/v1'; this.currentTab = 'dashboard'; this.cas = []; this.subcas = []; this.certificates = []; this.init(); } init() { this.bindEvents(); this.showTab('dashboard'); } bindEvents() { // Tab navigation document.querySelectorAll('.nav-btn').forEach(btn => { btn.addEventListener('click', (e) => { const tab = e.target.dataset.tab || e.target.closest('.nav-btn').dataset.tab; this.showTab(tab); }); }); // Form submissions const caForm = document.getElementById('createCAForm'); const subcaForm = document.getElementById('createSubCAForm'); const certForm = document.getElementById('createCertForm'); if (caForm) caForm.addEventListener('submit', (e) => this.handleCreateCA(e)); if (subcaForm) subcaForm.addEventListener('submit', (e) => this.handleCreateSubCA(e)); if (certForm) certForm.addEventListener('submit', (e) => this.handleCreateCertificate(e)); // Close modal buttons document.addEventListener('click', (e) => { if (e.target.classList.contains('close-btn') || e.target.closest('.close-btn')) { this.hideModal(); } }); // Click outside modal window.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { this.hideModal(); } }); } showTab(tabName) { console.log(`Showing tab: ${tabName}`); // Hide all tabs document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.add('hidden'); }); // Update active button document.querySelectorAll('.nav-btn').forEach(btn => { btn.classList.remove('active'); }); const activeBtn = document.querySelector(`[data-tab="${tabName}"]`); if (activeBtn) activeBtn.classList.add('active'); // Show selected tab const tabElement = document.getElementById(tabName + 'Tab'); if (tabElement) { tabElement.classList.remove('hidden'); } this.currentTab = tabName; // Load data for tab setTimeout(() => this.loadTabData(tabName), 50); } async loadTabData(tabName) { switch(tabName) { case 'dashboard': await this.loadDashboard(); break; case 'cas': await this.loadCAs(); break; case 'subcas': await this.loadSubCAs(); break; case 'certificates': await this.loadCertificates(); break; } } async loadDashboard() { const content = document.getElementById('dashboardContent'); if (!content) return; content.innerHTML = '
Loading dashboard...
'; try { // Charger les données await this.fetchAllData(); content.innerHTML = `

Root CAs

${this.cas.filter(c => c.is_root).length}

Sub CAs

${this.subcas.length}

Certificates

${this.certificates.length}

Active

${this.certificates.filter(c => !c.revoked && new Date(c.valid_to) > new Date()).length}

Recent Certificates

${this.certificates.length > 0 ? ` ${this.certificates.slice(0, 5).map(cert => ` `).join('')}
NameTypeIssuedExpiresStatus
${cert.common_name} ${cert.type} ${new Date(cert.created_at).toLocaleDateString()} ${new Date(cert.valid_to).toLocaleDateString()} ${cert.revoked ? 'Revoked' : new Date(cert.valid_to) > new Date() ? 'Valid' : 'Expired'}
` : '

No certificates yet

'}
`; } catch (error) { console.error('Dashboard error:', error); content.innerHTML = '
Failed to load dashboard
'; } } async loadCAs() { const content = document.getElementById('casContent'); if (!content) return; content.innerHTML = '
Loading CAs...
'; try { await this.fetchCAs(); content.innerHTML = `

Certificate Authorities

${this.cas.length > 0 ? ` ${this.cas.map(ca => ` `).join('')}
Name Common Name Organization Valid From Valid To Actions
${ca.name} ${ca.common_name} ${ca.organization} ${new Date(ca.valid_from).toLocaleDateString()} ${new Date(ca.valid_to).toLocaleDateString()}
` : `

No Certificate Authorities found

`} `; } catch (error) { console.error('CAs error:', error); content.innerHTML = '
Failed to load CAs
'; } } async loadSubCAs() { const content = document.getElementById('subcasContent'); if (!content) return; content.innerHTML = '
Loading Sub CAs...
'; try { await this.fetchSubCAs(); await this.fetchCAs(); // Need CAs for parent info content.innerHTML = `

Sub Certificate Authorities

${this.subcas.length > 0 ? ` ${this.subcas.map(subca => { const parent = this.cas.find(ca => ca.id === subca.parent_ca_id); return ` `; }).join('')}
Name Common Name Parent CA Valid From Valid To Actions
${subca.name} ${subca.common_name} ${parent ? parent.name : 'Unknown'} ${new Date(subca.valid_from).toLocaleDateString()} ${new Date(subca.valid_to).toLocaleDateString()}
` : `

No Sub Certificate Authorities found

${this.cas.length > 0 ? ` ` : '

Create a CA first to create Sub CAs

'}
`} `; } catch (error) { console.error('SubCAs error:', error); content.innerHTML = '
Failed to load Sub CAs
'; } } async loadCertificates() { const content = document.getElementById('certsContent'); if (!content) return; content.innerHTML = '
Loading Certificates...
'; try { await this.fetchCertificates(); await this.fetchCAs(); await this.fetchSubCAs(); content.innerHTML = `

Certificates

${this.certificates.length > 0 ? ` ${this.certificates.map(cert => { const issuer = [...this.cas, ...this.subcas].find(i => i.id === cert.issuer_ca_id); const status = cert.revoked ? 'revoked' : new Date(cert.valid_to) > new Date() ? 'valid' : 'expired'; return ` `; }).join('')}
Common Name Type Issuer Issued Expires Status Actions
${cert.common_name} ${cert.type} ${issuer ? issuer.name : 'Unknown'} ${new Date(cert.created_at).toLocaleDateString()} ${new Date(cert.valid_to).toLocaleDateString()} ${status.charAt(0).toUpperCase() + status.slice(1)} ${!cert.revoked ? `` : ''}
` : `

No certificates found

${this.cas.length > 0 || this.subcas.length > 0 ? ` ` : '

Create a CA or Sub CA first

'}
`} `; } catch (error) { console.error('Certificates error:', error); content.innerHTML = '
Failed to load certificates
'; } } async fetchAllData() { try { const [cas, subcas, certs] = await Promise.all([ this.fetchCAs(), this.fetchSubCAs(), this.fetchCertificates() ]); return { cas, subcas, certs }; } catch (error) { console.error('Error fetching all data:', error); throw error; } } async fetchCAs() { try { console.log('Fetching CAs from:', `${this.apiBase}/cas`); const response = await fetch(`${this.apiBase}/cas`); console.log('CAs response status:', response.status); if (!response.ok) { console.warn(`CAs fetch failed: ${response.status} ${response.statusText}`); this.cas = []; return this.cas; } const data = await response.json(); console.log('CAs raw data:', data); // S'assurer que c'est un tableau this.cas = Array.isArray(data) ? data : []; console.log(`Loaded ${this.cas.length} CAs`); return this.cas; } catch (error) { console.error('CAs fetch error:', error); this.cas = []; return this.cas; } } async fetchSubCAs() { try { console.log('Fetching SubCAs from:', `${this.apiBase}/subcas`); const response = await fetch(`${this.apiBase}/subcas`); console.log('SubCAs response status:', response.status); if (!response.ok) { console.warn(`SubCAs fetch failed: ${response.status} ${response.statusText}`); this.subcas = []; return this.subcas; } const data = await response.json(); console.log('SubCAs raw data:', data); // S'assurer que c'est un tableau this.subcas = Array.isArray(data) ? data : []; console.log(`Loaded ${this.subcas.length} SubCAs`); return this.subcas; } catch (error) { console.error('SubCAs fetch error:', error); this.subcas = []; return this.subcas; } } async fetchCertificates() { try { console.log('Fetching certificates...'); const response = await fetch(`${this.apiBase}/certificates`); if (!response.ok) { console.warn(`Certificates fetch failed: ${response.status}`); this.certificates = []; return this.certificates; } const data = await response.json(); this.certificates = Array.isArray(data) ? data : []; console.log(`Loaded ${this.certificates.length} certificates`); return this.certificates; } catch (error) { console.warn('Certificates fetch error:', error); this.certificates = []; return this.certificates; } } async showCreateCAModal() { const modal = document.getElementById('createCAModal'); if (modal) modal.classList.remove('hidden'); } async showCreateSubCAModal() { try { // Charger les CAs pour le dropdown await this.fetchCAs(); if (this.cas.length === 0) { this.showError('Create a CA first before creating a Sub CA'); return; } // Mettre à jour le dropdown const dropdown = document.getElementById('parentCA'); if (dropdown) { dropdown.innerHTML = this.cas.map(ca => `` ).join(''); } const modal = document.getElementById('createSubCAModal'); if (modal) modal.classList.remove('hidden'); } catch (error) { console.error('Error showing SubCA modal:', error); this.showError('Failed to load CAs'); } } async showCreateCertModal() { console.log('=== showCreateCertModal START ==='); try { // 1. Charger les données console.log('Loading issuers...'); await this.fetchCAs(); await this.fetchSubCAs(); // 2. Vérifier les données console.log(`CAs: ${this.cas.length} items`, this.cas); console.log(`SubCAs: ${this.subcas.length} items`, this.subcas); // 3. Combiner tous les émetteurs const allIssuers = []; // Ajouter les CAs if (this.cas && Array.isArray(this.cas)) { this.cas.forEach(ca => { if (ca && ca.id) { allIssuers.push({ id: ca.id, name: ca.name || ca.common_name || 'Unnamed CA', common_name: ca.common_name || 'No CN', type: 'CA' }); } }); } // Ajouter les SubCAs if (this.subcas && Array.isArray(this.subcas)) { this.subcas.forEach(subca => { if (subca && subca.id) { allIssuers.push({ id: subca.id, name: subca.name || subca.common_name || 'Unnamed SubCA', common_name: subca.common_name || 'No CN', type: 'SubCA' }); } }); } console.log('All issuers combined:', allIssuers); if (allIssuers.length === 0) { this.showError('Please create a CA or Sub CA first.'); return; } // 4. Remplir le dropdown const dropdown = document.getElementById('issuerCA'); if (!dropdown) { console.error('ERROR: Dropdown #issuerCA not found in DOM!'); // Vérifier si l'élément existe avec un autre ID console.log('Available select elements:', document.querySelectorAll('select')); return; } console.log('Dropdown found, populating...'); // Sauvegarder la sélection actuelle const currentValue = dropdown.value; // Vider et remplir dropdown.innerHTML = ''; allIssuers.forEach(issuer => { const option = document.createElement('option'); option.value = issuer.id; option.textContent = `${issuer.name} (${issuer.type})`; dropdown.appendChild(option); }); // Restaurer la sélection si possible if (currentValue && allIssuers.some(i => i.id === currentValue)) { dropdown.value = currentValue; } console.log(`Dropdown populated with ${allIssuers.length} options`); console.log('Dropdown HTML:', dropdown.innerHTML); // 5. Afficher la modal const modal = document.getElementById('createCertModal'); if (modal) { modal.classList.remove('hidden'); console.log('Modal shown'); // Focus sur le premier champ setTimeout(() => { const firstInput = modal.querySelector('input, select'); if (firstInput) firstInput.focus(); }, 100); } else { console.error('ERROR: Modal #createCertModal not found!'); } } catch (error) { console.error('Error in showCreateCertModal:', error); this.showError('Failed to open certificate form: ' + error.message); } console.log('=== showCreateCertModal END ==='); } hideModal() { document.querySelectorAll('.modal').forEach(modal => { modal.classList.add('hidden'); }); } async handleCreateCA(e) { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); try { const response = await fetch(`${this.apiBase}/cas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: data.name, common_name: data.common_name, organization: data.organization, country: data.country, province: data.province || '', locality: data.locality || '', email: data.email || '', key_size: parseInt(data.key_size) || 4096, valid_years: parseInt(data.valid_years) || 10, is_root: data.is_root === 'true' }) }); if (response.ok) { this.showSuccess('CA created successfully'); this.hideModal(); e.target.reset(); await this.fetchAllData(); this.showTab(this.currentTab); } else { const error = await response.json(); throw new Error(error.error || 'Failed to create CA'); } } catch (error) { console.error('CA creation error:', error); this.showError('Failed to create CA: ' + error.message); } } async handleCreateSubCA(e) { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); try { const response = await fetch(`${this.apiBase}/subcas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: data.name, common_name: data.common_name, organization: data.organization, email: data.email || '', country: data.country, province: data.province || '', locality: data.locality || '', parent_ca_id: data.parent_ca_id, key_size: parseInt(data.key_size) || 4096, valid_years: parseInt(data.valid_years) || 5 }) }); if (response.ok) { this.showSuccess('Sub CA created successfully'); this.hideModal(); e.target.reset(); await this.fetchAllData(); this.showTab(this.currentTab); } else { const error = await response.json(); throw new Error(error.error || 'Failed to create Sub CA'); } } catch (error) { console.error('SubCA creation error:', error); this.showError('Failed to create Sub CA: ' + error.message); } } async handleCreateCertificate(e) { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); try { const dnsNames = data.dns_names ? data.dns_names.split(',').map(s => s.trim()).filter(s => s) : []; const ipAddresses = data.ip_addresses ? data.ip_addresses.split(',').map(s => s.trim()).filter(s => s) : []; const response = await fetch(`${this.apiBase}/certificates`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ common_name: data.common_name, type: data.type, dns_names: dnsNames, ip_addresses: ipAddresses, issuer_ca_id: data.issuer_ca_id, key_size: parseInt(data.key_size) || 2048, valid_days: parseInt(data.valid_days) || 365 }) }); if (response.ok) { this.showSuccess('Certificate created successfully'); this.hideModal(); e.target.reset(); await this.fetchAllData(); this.showTab(this.currentTab); } else { const error = await response.json(); throw new Error(error.error || 'Failed to create certificate'); } } catch (error) { console.error('Certificate creation error:', error); this.showError('Failed to create certificate: ' + error.message); } } async deleteCA(id) { if (!confirm('Delete this CA?')) return; try { const response = await fetch(`${this.apiBase}/cas/${id}`, { method: 'DELETE' }); if (response.ok) { this.showSuccess('CA deleted'); await this.fetchAllData(); this.showTab(this.currentTab); } else { throw new Error('Delete failed'); } } catch (error) { this.showError('Failed to delete CA'); } } async deleteSubCA(id) { if (!confirm('Delete this Sub CA?')) return; try { const response = await fetch(`${this.apiBase}/subcas/${id}`, { method: 'DELETE' }); if (response.ok) { this.showSuccess('Sub CA deleted'); await this.fetchAllData(); this.showTab(this.currentTab); } else { throw new Error('Delete failed'); } } catch (error) { this.showError('Failed to delete Sub CA'); } } async deleteCertificate(id) { if (!confirm('Delete this certificate?')) return; try { const response = await fetch(`${this.apiBase}/certificates/${id}`, { method: 'DELETE' }); if (response.ok) { this.showSuccess('Certificate deleted'); await this.fetchAllData(); this.showTab(this.currentTab); } else { throw new Error('Delete failed'); } } catch (error) { this.showError('Failed to delete certificate'); } } async revokeCertificate(id) { const reason = prompt('Revocation reason:') || 'Administrative revocation'; if (!reason) return; try { const response = await fetch(`${this.apiBase}/certificates/${id}/revoke`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reason }) }); if (response.ok) { this.showSuccess('Certificate revoked'); await this.fetchAllData(); this.showTab(this.currentTab); } else { throw new Error('Revocation failed'); } } catch (error) { this.showError('Failed to revoke certificate'); } } downloadCertificate(id) { window.open(`${this.apiBase}/certificates/${id}/download/cert`, '_blank'); } async viewCA(id) { try { const response = await fetch(`${this.apiBase}/cas/${id}`); if (!response.ok) throw new Error('Not found'); const ca = await response.json(); this.showModal('CA Details', `

${ca.name}

Common Name: ${ca.common_name}

Organization: ${ca.organization}

Valid From: ${new Date(ca.valid_from).toLocaleString()}

Valid To: ${new Date(ca.valid_to).toLocaleString()}

Serial: ${ca.serial_number}

`); } catch (error) { this.showError('Failed to load CA details'); } } async viewSubCA(id) { try { const response = await fetch(`${this.apiBase}/subcas/${id}`); if (!response.ok) throw new Error('Not found'); const subca = await response.json(); this.showModal('Sub CA Details', `

${subca.name}

Common Name: ${subca.common_name}

Organization: ${subca.organization}

Valid From: ${new Date(subca.valid_from).toLocaleString()}

Valid To: ${new Date(subca.valid_to).toLocaleString()}

Serial: ${subca.serial_number}

`); } catch (error) { this.showError('Failed to load Sub CA details'); } } async viewCertificate(id) { try { const response = await fetch(`${this.apiBase}/certificates/${id}`); if (!response.ok) throw new Error('Not found'); const cert = await response.json(); this.showModal('Certificate Details', `

${cert.common_name}

Type: ${cert.type}

Valid From: ${new Date(cert.valid_from).toLocaleString()}

Valid To: ${new Date(cert.valid_to).toLocaleString()}

Status: ${cert.revoked ? 'Revoked' : new Date(cert.valid_to) > new Date() ? 'Valid' : 'Expired'}

Serial: ${cert.serial_number}

`); } catch (error) { this.showError('Failed to load certificate details'); } } showModal(title, content) { const modal = document.getElementById('viewModal'); if (!modal) return; modal.querySelector('h2').textContent = title; modal.querySelector('.modal-body').innerHTML = content; modal.classList.remove('hidden'); } showSuccess(message) { this.showAlert(message, 'success'); } showError(message) { this.showAlert(message, 'error'); } showAlert(message, type) { // Remove existing alerts document.querySelectorAll('.alert').forEach(a => a.remove()); const alert = document.createElement('div'); alert.className = `alert alert-${type}`; alert.textContent = message; alert.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 15px 20px; border-radius: 5px; z-index: 10000; animation: slideIn 0.3s ease; `; document.body.appendChild(alert); setTimeout(() => { alert.style.animation = 'slideOut 0.3s ease'; setTimeout(() => alert.remove(), 300); }, 5000); } } // Initialize when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.pki = new PKIManager(); });