Files
flowdeck/static/js/import.js
T
bruno 8d0d69e7b8
FlowDeck CI / test (push) Failing after 3h9m51s
FlowDeck CI / lint (push) Successful in 1m54s
FlowDeck CI / docker (push) Skipped
fix: A27 lint terminé — eslint 0/0 (285 warnings nettoyés) (v7.25.0)
3 familles, 13 fichiers (+153/−167) :

1. no-empty ×70 = TOUS des `catch (x) {}` vides → `catch { /* volontaire */ }`
   (binding optionnel ES2019 + commentaire : passe no-empty ET
   no-unused-vars, zéro changement de comportement).

2. no-unused-vars ×171 :
   - bindings de catch inutilisés retirés (e/err/ex/e2/e3)
   - 24 lignes mortes déterministes, chaque suppression validée par assert
     sur le texte exact (`var self = this` ×8, `var lang`, `var acc`,
     `var today`, `var path/restored/files/resolved/items/clickEl`,
     `uid()`/`propName()` sans un seul appel, `.then` + `resolved++`
     compteurs jamais lus)
   - `/* exported */` sur les 10 fonctions appelées depuis les attributs
     HTML des templates (vérifiées par grep : 1 template chacune) :
     setActiveTab/kanbanBoard/filterSystem/sortSystem/newIssueForm/
     showNewIssue, importWizard, libraryPage, workspacesPage, settingsInit

3. no-undef ×44 = vrais globaux déclarés dans eslint.config.mjs
   (getSvgIcon = script inline de base.html, TextDecoder = API navigateur,
   Prism = CDN) + 2 vrais correctifs :
   - settings.js : `typeof toast === 'function'` = guard TOUJOURS faux
     (pas de toast global) → les toasts timezone/SAML ne s'affichaient
     jamais → `window.showToast` (2 sites)
   - local_workspace.js : `_wsInitData = window._wsInitData`
     (auto-affectation sans effet, global implicite) supprimé

eslint static/js : **0 erreur / 0 warning** (285 → 0) · node --check vert
sur tous les fichiers · suite **1089/1089** · ruff OK · docs à jour
2026-10-01 21:30:37 -04:00

160 lines
6.7 KiB
JavaScript

/* exported importWizard -- appeles depuis les attributs HTML des templates */
function importWizard() {
return {
sources: [],
sourceId: '',
files: [],
dragOver: false,
mode: 'skip',
busy: false,
progress: 0,
error: '',
preview: null,
report: null,
mapping: {},
csrf: '',
urlValue: '',
urlError: '',
forgeProvider: 'gitea',
forgeOwner: '',
forgeRepo: '',
forgeState: 'all',
forgeError: '',
types: ['text','number','date','checkbox','email','url','phone','select','multi_select','status'],
async init() {
try {
const r = await fetch('/api/import/sources');
this.sources = (await r.json()).sources || [];
} catch { /* volontaire */ }
try {
const c = await fetch('/api/csrf-token');
this.csrf = (await c.json()).csrf_token;
} catch { /* volontaire */ }
},
humanSize(n) { return n > 1048576 ? (n/1048576).toFixed(1)+' Mo' : Math.max(1, Math.round(n/1024))+' Ko'; },
addFiles(list) {
for (const f of list) this.files.push({file:f, name:f.name, size:f.size, status:'queued'});
this.preview = null; this.report = null; this.error = '';
},
onFiles(e) { this.addFiles(e.target.files); e.target.value=''; },
onDrop(e) { this.dragOver=false; this.addFiles(e.dataTransfer.files); },
buildForm(f) {
const fd = new FormData();
fd.append('file', f.file);
if (this.sourceId) fd.append('source', this.sourceId);
fd.append('mode', this.mode);
return fd;
},
async doPreview() {
this.busy = true; this.error=''; this.report=null; this.progress=10;
try {
let merged = null;
for (let i=0;i<this.files.length;i++) {
const r = await fetch('/api/import/preview', {method:'POST', headers:{'X-CSRF-Token':this.csrf}, body:this.buildForm(this.files[i])});
const d = await r.json();
if (!r.ok) { this.error = (this.files[i].name+': '+(d.detail||'Erreur')); continue; }
if (!merged) merged = {source_label:d.source_label, pages:[], warnings:[], stats:{}};
merged.pages.push(...(d.pages||[]));
merged.warnings.push(...(d.warnings||[]));
this.progress = Math.round(((i+1)/this.files.length)*100);
}
this.preview = merged;
this.mapping = {};
(merged ? merged.pages : []).forEach(p => (p.schema||[]).forEach(c => { this.mapping[c.name]=c.type; }));
} catch { this.error = 'Erreur réseau'; }
finally { this.busy = false; setTimeout(()=>{this.progress=0;},800); }
},
async importOne(f) {
f.status = 'running';
const fd = this.buildForm(f);
if (Object.keys(this.mapping).length) fd.append('mapping', JSON.stringify(this.mapping));
const big = f.size > 5*1024*1024;
try {
if (big) {
fd.append('async','true');
const r = await fetch('/api/import/run', {method:'POST', headers:{'X-CSRF-Token':this.csrf}, body:fd});
const d = await r.json();
if (!r.ok) { f.status='error'; f.error=d.detail||'Erreur'; return null; }
return await this.pollJob(d.job_id, f);
}
const r = await fetch('/api/import/run', {method:'POST', headers:{'X-CSRF-Token':this.csrf}, body:fd});
const d = await r.json();
if (!r.ok) { f.status='error'; f.error=d.detail||'Erreur'; return null; }
f.status = (d.status==='partial' ? 'partial' : 'done');
return d;
} catch { f.status='error'; f.error='Erreur réseau'; return null; }
},
async doImport() {
this.busy = true; this.error=''; this.report=null; this.progress=0;
const aggregate = {pages_created:0,pages_updated:0,collections_created:0,rows_created:0,
attachments:0,skipped:0,warnings:[],errors:[],per_file:[]};
for (let i=0;i<this.files.length;i++) {
const d = await this.importOne(this.files[i]);
if (d) {
for (const k of ['pages_created','pages_updated','collections_created','rows_created','attachments','skipped'])
aggregate[k] += (d[k]||0);
aggregate.warnings.push(...(d.warnings||[]));
aggregate.errors.push(...(d.errors||[]));
aggregate.per_file.push({filename:this.files[i].name, report:d});
} else {
aggregate.errors.push({title:this.files[i].name, error:this.files[i].error||'Erreur'});
aggregate.per_file.push({filename:this.files[i].name, report:{status:'error'}});
}
this.progress = Math.round(((i+1)/this.files.length)*100);
}
this.report = aggregate;
this.busy = false;
},
async doUrl() {
this.busy = true; this.urlError = ''; this.report = null; this.progress = 40;
try {
const r = await fetch('/api/import/url', {
method:'POST', headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrf},
body: JSON.stringify({url:this.urlValue.trim()})
});
const d = await r.json();
if (!r.ok) { this.urlError = d.detail || 'Erreur'; return; }
this.report = d; this.progress = 100;
} catch { this.urlError = 'Erreur réseau'; }
finally { this.busy = false; }
},
async doForge(kind) {
this.busy = true; this.forgeError = ''; this.report = null; this.progress = 30;
const endpoint = kind === 'repo' ? '/api/import/forge-repo' : '/api/import/forge';
try {
const r = await fetch(endpoint, {
method:'POST', headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrf},
body: JSON.stringify({
provider:this.forgeProvider, owner:this.forgeOwner.trim(),
repo:this.forgeRepo.trim(), state:this.forgeState
})
});
const d = await r.json();
if (!r.ok) { this.forgeError = d.detail || 'Erreur'; return; }
this.report = d; this.progress = 100;
} catch { this.forgeError = 'Erreur réseau'; }
finally { this.busy = false; }
},
async pollJob(jobId, f) {
for (let i=0;i<600;i++) {
await new Promise(res => setTimeout(res, 700));
const r = await fetch('/api/import/jobs/'+jobId);
const j = await r.json();
if (j.status === 'done') { if (f) f.status='done'; return j.report; }
if (j.status === 'error') { if (f) { f.status='error'; f.error=j.error; } return null; }
}
if (f) { f.status='error'; f.error='Délai dépassé'; }
return null;
},
downloadReport() {
const blob = new Blob([JSON.stringify(this.report, null, 2)], {type:'application/json'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'flowdeck-import-report.json';
a.click();
URL.revokeObjectURL(a.href);
}
};
}