Phase 2 coffre: bouton Importer l'existant (import puis purge confirmee)
This commit is contained in:
@@ -18,6 +18,11 @@
|
||||
* _copieManuelle) passe par un consommateur temporaire créé puis révoqué
|
||||
* immédiatement, avec distribution auditée ; il n'existe pas encore
|
||||
* d'endpoint de distribution par session utilisateur côté backend.
|
||||
* - Phase 2 (issue #20) : « Importer l'existant » migre les clés de
|
||||
* data/api_keys.json vers le coffre d'instance « ObsiGate interne »,
|
||||
* en deux temps (import, puis purge confirmée). Aucune valeur ne
|
||||
* transite jamais par cette vue : l'aperçu et les résultats ne
|
||||
* contiennent que des noms et des comptes (has_value).
|
||||
* - La vue est rendue directement dans #content-area : elle ne passe ni
|
||||
* par TabManager.openNav (navigation de fichiers), ni par l'indexeur,
|
||||
* ni par la recherche, ni par le graphe.
|
||||
@@ -865,6 +870,83 @@ async function copieManuelle(s) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Phase 2 — « Importer l'existant » (admin seulement, deux temps).
|
||||
* 1. Aperçu (métadonnées seules, jamais de valeur) puis import vers le
|
||||
* coffre d'instance. L'import ne supprime rien dans api_keys.json.
|
||||
* 2. Purge du clair, uniquement après confirmation explicite, et
|
||||
* uniquement pour les noms effectivement migrés. L'avertissement est
|
||||
* affiché avant l'import : les lecteurs actuels (IA, outils) lisent
|
||||
* encore api_keys.json, la purge les prive donc de la clé tant que
|
||||
* leur lecteur n'est pas branché sur le coffre (voir la documentation
|
||||
* de sauvegarde/restauration). */
|
||||
async function importerExistant() {
|
||||
if (!isAdmin()) return;
|
||||
let apercu;
|
||||
try {
|
||||
apercu = await api('/api/coffres/migration/api-keys');
|
||||
} catch (e) {
|
||||
showToast(e.message || t('common.error'), 'error');
|
||||
return;
|
||||
}
|
||||
const candidats = apercu.candidats || [];
|
||||
const lignes = candidats.map((c) => {
|
||||
const etat = c.deja_existant
|
||||
? t('coffres.import_already_exists')
|
||||
: (c.has_value ? t('coffres.import_has_value') : t('coffres.import_no_value'));
|
||||
return `${c.nom} (${c.service}) — ${etat}`;
|
||||
});
|
||||
const body = el('div', {}, [
|
||||
text('p', t('coffres.import_warning'), 'modal-hint'),
|
||||
...lignes.map((ligne) => text('p', ligne, '')),
|
||||
]);
|
||||
const m = openModal(t('coffres.import_title'), body, [
|
||||
modalBtn(t('common.cancel'), '', () => m.close()),
|
||||
modalBtn(t('coffres.import_existing'), 'primary', async () => {
|
||||
let resultat;
|
||||
try {
|
||||
resultat = await api('/api/coffres/migration/api-keys/import', { method: 'POST', body: '{}' });
|
||||
} catch (e) {
|
||||
showToast(e.message || t('common.error'), 'error');
|
||||
return;
|
||||
}
|
||||
m.close();
|
||||
const migres = resultat.migres || [];
|
||||
const resume = [
|
||||
`${t('coffres.import_done')} — ${t('coffres.import_migrated')}: ${migres.length}, ${t('coffres.import_already_exists')}: ${(resultat.deja_existants || []).length}, ${t('coffres.import_no_value')}: ${(resultat.sans_valeur || []).length}`,
|
||||
...migres.map((nom) => `${nom} — ${t('coffres.import_migrated')}`),
|
||||
];
|
||||
const rb = el('div', {}, resume.map((ligne) => text('p', ligne, '')));
|
||||
const boutons = [modalBtn(t('common.close'), 'primary', async () => {
|
||||
rm.close();
|
||||
await openCoffresView('obsigate-interne');
|
||||
})];
|
||||
if (migres.length) {
|
||||
boutons.unshift(modalBtn(t('coffres.import_purge'), 'danger', async () => {
|
||||
const ok = await showConfirm({
|
||||
title: t('coffres.import_purge'),
|
||||
message: `${t('coffres.import_purge_confirm')} ${migres.join(', ')}`,
|
||||
danger: true,
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
const purge = await api('/api/coffres/migration/api-keys/purger', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ confirmer: true, noms: migres }),
|
||||
});
|
||||
showToast(`${t('coffres.import_purged')} (${(purge.supprimes || []).length})`, 'success');
|
||||
} catch (e) {
|
||||
showToast(e.message || t('common.error'), 'error');
|
||||
}
|
||||
rm.close();
|
||||
await openCoffresView('obsigate-interne');
|
||||
}));
|
||||
}
|
||||
const rm = openModal(t('coffres.import_title'), rb, boutons);
|
||||
void rm;
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
function openKekModal(mode) {
|
||||
if (!isAdmin()) return;
|
||||
if (mode === 'generer') {
|
||||
@@ -1002,6 +1084,12 @@ export async function openCoffresView(slug, options = {}) {
|
||||
});
|
||||
toolbar.appendChild(search);
|
||||
toolbar.appendChild(modalBtn(t('coffres.new_secret'), 'primary', () => openCreateSecretModal(coffre.slug)));
|
||||
// Phase 2 : migration api_keys.json -> coffre d'instance (admin seulement).
|
||||
// Le bouton est visible quel que soit le coffre affiché : la cible est
|
||||
// toujours le coffre d'instance « ObsiGate interne ».
|
||||
if (isAdmin()) {
|
||||
toolbar.appendChild(modalBtn(t('coffres.import_existing'), '', () => importerExistant()));
|
||||
}
|
||||
toolbar.appendChild(modalBtn(t('coffres.dek_rotate'), '', async () => {
|
||||
const ok = await showConfirm({ title: t('coffres.dek_rotate'), message: t('coffres.confirm_dek_rotate'), danger: true });
|
||||
if (!ok) return;
|
||||
|
||||
Reference in New Issue
Block a user