(function(){ const API_BASE = '/api/v1'; let token = localStorage.getItem('pki_token') || ''; function $(id){ return document.getElementById(id); } function show(el){ el.classList.remove('hidden'); } function hide(el){ el.classList.add('hidden'); } function showMessage(msg, isError){ const m = $('message'); m.textContent = msg; m.className = 'card'; if(isError) m.classList.add('error'); show(m); setTimeout(()=>{ hide(m); }, 7000); } function apiFetch(path, opts={}){ opts.headers = opts.headers || {}; opts.headers['Content-Type'] = 'application/json'; if(token) opts.headers['Authorization'] = 'Bearer ' + token; return fetch(API_BASE + path, opts).then(async res => { const ct = res.headers.get('content-type') || ''; if(!res.ok){ let body = await res.text(); throw new Error(body || res.statusText); } if(ct.includes('application/json')) return res.json(); return res.blob(); }); } function renderTable(items, type){ const list = $('list'); list.innerHTML = ''; const h = document.createElement('h3'); h.textContent = type === 'ca' ? 'Autorités de Certification' : 'Certificats'; list.appendChild(h); const table = document.createElement('table'); table.className = 'table'; const thead = document.createElement('thead'); const headRow = document.createElement('tr'); ['ID','Sujet','Émetteur','Not After','Actions'].forEach(t=>{ const th=document.createElement('th'); th.textContent=t; headRow.appendChild(th); }); thead.appendChild(headRow); table.appendChild(thead); const tbody = document.createElement('tbody'); items.forEach(it=>{ const tr = document.createElement('tr'); const idCell = document.createElement('td'); idCell.textContent = it.id || it.ID || it.Id || ''; const subj = document.createElement('td'); subj.textContent = it.subject || it.Subject || it.common_name || '-'; const issuer = document.createElement('td'); issuer.textContent = it.issuer || it.issuer || '-'; const na = document.createElement('td'); na.textContent = it.not_after || it.notAfter || it.notAfter || '-'; const actions = document.createElement('td'); const btnView = document.createElement('button'); btnView.textContent = 'Voir'; btnView.onclick = ()=> viewDetails(it.id || it.ID || it.Id); actions.appendChild(btnView); if(type === 'cert'){ const btnExport = document.createElement('button'); btnExport.textContent = 'Export PEM'; btnExport.onclick = ()=> exportCertPEM(it.id || it.ID || it.Id); actions.appendChild(btnExport); const btnRevoke = document.createElement('button'); btnRevoke.textContent = 'Révoquer'; btnRevoke.onclick = ()=> revokeCert(it.id || it.ID || it.Id); actions.appendChild(btnRevoke); } tr.appendChild(idCell); tr.appendChild(subj); tr.appendChild(issuer); tr.appendChild(na); tr.appendChild(actions); tbody.appendChild(tr); }); table.appendChild(tbody); list.appendChild(table); show(list); } function setTokenFromInput(){ const t = $('token').value.trim(); token = t; localStorage.setItem('pki_token', token); showMessage('Token enregistré.'); } async function loadCAs(){ try{ const res = await apiFetch('/ca'); const cas = res.cas || res.list || res; renderTable(cas, 'ca'); }catch(e){ showMessage('Erreur loadCAs: '+e.message, true); } } async function loadCerts(){ try{ const res = await apiFetch('/certificates'); const certs = res.certificates || res.list || res; renderTable(certs, 'cert'); }catch(e){ showMessage('Erreur loadCerts: '+e.message, true); } } function showCreateCAForm(){ const f = $('form'); f.innerHTML = ''; const h = document.createElement('h3'); h.textContent = 'Créer une CA'; f.appendChild(h); const subject = document.createElement('input'); subject.placeholder='CN=Root CA,O=Example,C=FR'; subject.id='ca_subject'; const days = document.createElement('input'); days.placeholder='validity_days'; days.type='number'; days.id='ca_days'; days.value=3650; const btn = document.createElement('button'); btn.textContent='Créer'; btn.onclick = async ()=>{ try{ const body = { subject: subject.value, validity_days: parseInt(days.value||0,10) }; const res = await apiFetch('/ca',{ method:'POST', body: JSON.stringify(body) }); showMessage('CA créée: '+(res.ca && res.ca.id)); loadCAs(); }catch(e){ showMessage('Erreur création CA: '+e.message, true); } }; f.appendChild(subject); f.appendChild(document.createElement('br')); f.appendChild(days); f.appendChild(document.createElement('br')); f.appendChild(btn); show(f); } function showCreateCertForm(){ const f = $('form'); f.innerHTML = ''; const h = document.createElement('h3'); h.textContent = 'Créer un Certificat (auto-signé)'; f.appendChild(h); const subject = document.createElement('input'); subject.placeholder='CN=server.example.com,O=Example,C=FR'; subject.id='cert_subject'; const days = document.createElement('input'); days.placeholder='validity_days'; days.type='number'; days.id='cert_days'; days.value=365; const btn = document.createElement('button'); btn.textContent='Créer'; btn.onclick = async ()=>{ try{ const body = { subject: subject.value, validity_days: parseInt(days.value||0,10) }; const res = await apiFetch('/certificates',{ method:'POST', body: JSON.stringify(body) }); showMessage('Certificat créé: '+(res.certificate && res.certificate.id)); loadCerts(); }catch(e){ showMessage('Erreur création cert: '+e.message, true); } }; f.appendChild(subject); f.appendChild(document.createElement('br')); f.appendChild(days); f.appendChild(document.createElement('br')); f.appendChild(btn); show(f); } async function viewDetails(id){ try{ const res = await apiFetch('/certificates/'+id); const cert = res.certificate || res; const d = $('details'); d.innerHTML = ''; const h = document.createElement('h3'); h.textContent = 'Détails Certificat'; d.appendChild(h); const pre = document.createElement('pre'); pre.textContent = JSON.stringify(cert, null, 2); d.appendChild(pre); show(d); }catch(e){ // peut-être une CA try{ const res = await apiFetch('/ca/'+id); const ca = res.ca || res; const d = $('details'); d.innerHTML=''; d.appendChild(document.createElement('h3')).textContent='Détails CA'; const pre = document.createElement('pre'); pre.textContent = JSON.stringify(ca, null, 2); d.appendChild(pre); show(d); }catch(er){ showMessage('Erreur viewDetails: '+er.message, true); } } } async function revokeCert(id){ if(!confirm('Confirmer la révocation du certificat '+id+' ?')) return; try{ const body = { certificate_id: id, reason: 'Revoked via UI' }; await apiFetch('/revoke',{ method:'POST', body: JSON.stringify(body) }); showMessage('Certificat révoqué: '+id); loadCerts(); }catch(e){ showMessage('Erreur revoke: '+e.message, true); } } async function exportCertPEM(id){ try{ const blob = await apiFetch('/certificates/'+id+'/export/pem'); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = id + '.pem'; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); }catch(e){ showMessage('Erreur export: '+e.message, true); } } function attachEvents(){ $('saveToken').onclick = setTokenFromInput; $('btnCAs').onclick = loadCAs; $('btnCerts').onclick = loadCerts; $('btnCreateCA').onclick = showCreateCAForm; $('btnCreateCert').onclick = showCreateCertForm; // prefills $('token').value = token; } // init document.addEventListener('DOMContentLoaded', ()=>{ attachEvents(); if(token) showMessage('Token chargé depuis localStorage.'); }); // export functions for debugging window.pkiUI = { loadCAs, loadCerts, viewDetails }; })();