diff --git a/CHANGELOG.md b/CHANGELOG.md index cfcd18b..ad5dbe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog - FlowDeck +## v7.25.0 (2026-10-01) — Audit : A27 CLOSED (eslint 0/0) + +### Fixed + +- **Toasts settings jamais affichés** : `settings.js` appelait + `typeof toast === 'function'` — guard **toujours faux** (le `toast` global + n'existait pas) → les retours « Timezone saved » / « SP metadata URL + copiée » ne s'affichaient jamais. → `window.showToast` (global `base.html`) +- `_wsInitData = window._wsInitData` (auto-affectation d'un nom non déclaré, + sans effet observable car `window._wsInitData` existe depuis L4) supprimé + +### Changed + +- **A27 lint terminé : `eslint static/js` = 0 erreur / 0 warning** (285 → 0, + 22 fichiers) : + · no-empty ×70 = `catch (x) {}` vides → `catch { /* volontaire */ }` + (binding optionnel ES2019 ; zéro changement de comportement) + · no-unused-vars ×171 = bindings de catch retirés + 24 lignes mortes + déterministes (assert sur texte exact avant suppression) + + `/* exported */` sur les 10 fonctions appelées depuis les attributs + HTML des templates (vérifiées par grep) + · no-undef ×44 = vrais globaux déclarés dans `eslint.config.mjs` + (getSvgIcon = inline base.html, TextDecoder = API, Prism = CDN) + +### Notes + +- `node --check` vert sur tous les fichiers ; suite complète **1089/1089** +- A27 reste ouvert uniquement pour : `base` 1 523 L structurel (inline par + nature, décision assumée) + ~500 L de petits blocs hors cibles + ## v7.24.0 (2026-10-01) — Audit : A27 extraction TERMINÉE (bilan -85 %) ### Changed diff --git a/ROADMAP.md b/ROADMAP.md index 4288009..99ecaa5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1147,7 +1147,7 @@ Quality DB views, Agent IA Palette → Realtime + E ### 🟡 P2 — Moyennes - [x] **A26 — Config piège** : (a) `FLOWDECK_STANDALONE` documenté dans `config.py:26` mais **jamais lu** (le champ réel est `STANDALONE`, pas de `env_prefix`, `extra="ignore"` avale la coquille) ; (b) `.env.example` documente `postgresql://…` mais `db_path` (`config.py:133`) retombe silencieusement sur SQLite ; (c) `db_path` fait `Path("/" + p)` (`config.py:132`) → pour `sqlite:////data/flowdeck.db` le résultat est le chemin UNC `\\data\flowdeck.db` (vérifié : inexistant) ; (d) `app_secret_key="change-me-to-random"` (`config.py:36`) + `gitea_oauth_client_secret="test-secret"` sans aucun garde au boot alors qu'il signe `flowdeck_session` (`session.py:14`). *Fix : 1 normalisation de `db_path` + `raise` au boot si secret par défaut + corriger le commentaire/`env_prefix`. Effort : **S**.* -- [x] **A27 — 13 900 lignes de JS inline — EXTRACTION TERMINÉE 2026-10-01 (11 874 L, -85 %) — reste : lint + base structurel**, ~3 800 livrées sur **chaque** page (`base.html` 1520 + `agent_panel` 1805 + `_icon_picker` 297 + `_header` 124 + `_notification_bell` 69), et **0 linté** : `eslint.config.mjs:50` ne couvre que `static/js/**/*.js` (soit `app.js` + `offline.js`), 2 blocs se neutralisent avec `/* eslint-disable */`. Grosseurs : `_page_editor_scripts` 2517, `local_workspace` 2030, `agent_panel` 1805, `base` 1520, `_database_table_scripts` 1323, `settings` 1093, `library` 1039. *Fix : extraire les gros partials vers `/static/js/*.js` (ils ne sont pas Jinja-interpolés) + ajouter les templates à eslint.* — **phase 1 faite 2026-10-01** : **7 templates sans Jinja → 9 fichiers `static/js/*.js` (4 243 lignes, -30 % du JS inline)** : agent_panel_1/2 (4 243… dont le 1 788 L livré sur CHAQUE page), library 1039, gitea_workspace 626, _icon_picker_1/2, _ctx_menu, import, workspaces — **un fichier par bloc** (ordre/timing identique, pas de defer, attributs conservés, `?v={{ asset_version }}`), `node --check` vert sur les 9, 0 script inline restant dans les cibles, suite 1089/1089. **Lint** : eslint **installé globalement** (`npm i -g eslint`, la config était déjà flat v9 sans dépendances) → `eslint static/js` = **0 erreur / 120 warnings** (no-unused-vars 69, no-empty 36, no-undef 15 — baseline à nettoyer opportunistiquement). **Phase 2a faite 2026-10-01 (l'éditeur, le plus gros bloc interpolé)** : `_page_editor_scripts` **2 516 L extraites** vers `static/js/page_editor_scripts.js` — recette « config JSON » : les 8 interpolations Jinja lisent `PD` = `JSON.parse(#page-data)` (le bloc JSON **existait déjà** juste avant le script) ; la route enrichit `page_data` de `updated_at`, `created_at`, `user_id`, `is_shared` (**dérivé hoisté : une seule expression pour le ctx ET le JSON**) et `clip_icon` (macro `fd_icon` rendue côté serveur) ; `workspace_key` reste vide comme avant (jamais défini dans ce ctx — parité). **Cumul A27 : 6 759 L extraites** (13 904 → 7 145 inline). **8 tests adaptés** (ils lisaient le template source → lisent maintenant `static/js/page_editor_scripts.js` ; `pageIsShared:true` → parsing du JSON `#page-data` → `is_shared is True`). **Phase 2b faite 2026-10-01** : **+3 801 L** extraites avec la même recette config JSON : `local_workspace` (2031 — `lw-config` : current_folder_id/workspace_id), `settings` (1093 — `st-config` : avatar/user/is_admin/auth_method, **2 routes rendent ce template**, expressions `or ""` préservées pour Undefined), `_page_editor_realtime` (531 — `rt-config` : SELF id/login/full_name/color), `board` (146 — `bd-config` : owner/repo/initial_view). BONUS sécurité : les valeurs passent par `|tojson` (échappement JSON) au lieu d'être interpolées dans des strings JS. **Cumul A27 : 10 560 L extraites** (13 904 → **3 344 restantes**), `node --check` vert ×4, suite 1089/1089, eslint **0 erreur / 279 warnings** (12 fichiers). **Phase 2c faite 2026-10-01 (BILAN : extraction TERMINÉE)** : `database_table` **1 314 L extraites** — le Jinja du bloc était confiné à la construction de l'objet de config (4 clés + `{% if collection_data %}`) → config JSON `null`-vs-objet (`#db-config`), le JS appelle `new DBInstance(container, PAGE_COLLECTION_ID, DB_CONFIG)` : les 2 branches Jinja disparaissent ; 2 tests adaptés (source → `database_table.js`) + `FlowDeckDB` dans src. **BILAN A27 : 11 874 L extraites en 4 phases** (4 243 + 2 516 + 3 801 + 1 314), **inline 13 904 → 2 022 L (-85 %)**, 22 fichiers `static/js/*.js`, `node --check` vert partout, suite 1089/1089. **Reste A27** : `base` 1 523 L structurel (`{% block %}`/`{% for %}` — **reste inline par nature**, décision assumée) + ~500 L de petits blocs hors cibles (`_header` 124, `welcome` 50, `card_detail` 47, `workspace` 46, `public_page` 45…) + nettoyage des **285 warnings eslint** (0 erreur). Effort : **L**. +- [x] **A27 — 13 900 lignes de JS inline — TERMINÉ 2026-10-01 (extraction 11 874 L / -85 % + eslint 0/0) — reste : structurel seul (base 1 523 L)**, ~3 800 livrées sur **chaque** page (`base.html` 1520 + `agent_panel` 1805 + `_icon_picker` 297 + `_header` 124 + `_notification_bell` 69), et **0 linté** : `eslint.config.mjs:50` ne couvre que `static/js/**/*.js` (soit `app.js` + `offline.js`), 2 blocs se neutralisent avec `/* eslint-disable */`. Grosseurs : `_page_editor_scripts` 2517, `local_workspace` 2030, `agent_panel` 1805, `base` 1520, `_database_table_scripts` 1323, `settings` 1093, `library` 1039. *Fix : extraire les gros partials vers `/static/js/*.js` (ils ne sont pas Jinja-interpolés) + ajouter les templates à eslint.* — **phase 1 faite 2026-10-01** : **7 templates sans Jinja → 9 fichiers `static/js/*.js` (4 243 lignes, -30 % du JS inline)** : agent_panel_1/2 (4 243… dont le 1 788 L livré sur CHAQUE page), library 1039, gitea_workspace 626, _icon_picker_1/2, _ctx_menu, import, workspaces — **un fichier par bloc** (ordre/timing identique, pas de defer, attributs conservés, `?v={{ asset_version }}`), `node --check` vert sur les 9, 0 script inline restant dans les cibles, suite 1089/1089. **Lint** : eslint **installé globalement** (`npm i -g eslint`, la config était déjà flat v9 sans dépendances) → `eslint static/js` = **0 erreur / 120 warnings** (no-unused-vars 69, no-empty 36, no-undef 15 — baseline à nettoyer opportunistiquement). **Phase 2a faite 2026-10-01 (l'éditeur, le plus gros bloc interpolé)** : `_page_editor_scripts` **2 516 L extraites** vers `static/js/page_editor_scripts.js` — recette « config JSON » : les 8 interpolations Jinja lisent `PD` = `JSON.parse(#page-data)` (le bloc JSON **existait déjà** juste avant le script) ; la route enrichit `page_data` de `updated_at`, `created_at`, `user_id`, `is_shared` (**dérivé hoisté : une seule expression pour le ctx ET le JSON**) et `clip_icon` (macro `fd_icon` rendue côté serveur) ; `workspace_key` reste vide comme avant (jamais défini dans ce ctx — parité). **Cumul A27 : 6 759 L extraites** (13 904 → 7 145 inline). **8 tests adaptés** (ils lisaient le template source → lisent maintenant `static/js/page_editor_scripts.js` ; `pageIsShared:true` → parsing du JSON `#page-data` → `is_shared is True`). **Phase 2b faite 2026-10-01** : **+3 801 L** extraites avec la même recette config JSON : `local_workspace` (2031 — `lw-config` : current_folder_id/workspace_id), `settings` (1093 — `st-config` : avatar/user/is_admin/auth_method, **2 routes rendent ce template**, expressions `or ""` préservées pour Undefined), `_page_editor_realtime` (531 — `rt-config` : SELF id/login/full_name/color), `board` (146 — `bd-config` : owner/repo/initial_view). BONUS sécurité : les valeurs passent par `|tojson` (échappement JSON) au lieu d'être interpolées dans des strings JS. **Cumul A27 : 10 560 L extraites** (13 904 → **3 344 restantes**), `node --check` vert ×4, suite 1089/1089, eslint **0 erreur / 279 warnings** (12 fichiers). **Phase 2c faite 2026-10-01 (BILAN : extraction TERMINÉE)** : `database_table` **1 314 L extraites** — le Jinja du bloc était confiné à la construction de l'objet de config (4 clés + `{% if collection_data %}`) → config JSON `null`-vs-objet (`#db-config`), le JS appelle `new DBInstance(container, PAGE_COLLECTION_ID, DB_CONFIG)` : les 2 branches Jinja disparaissent ; 2 tests adaptés (source → `database_table.js`) + `FlowDeckDB` dans src. **BILAN A27 : 11 874 L extraites en 4 phases** (4 243 + 2 516 + 3 801 + 1 314), **inline 13 904 → 2 022 L (-85 %)**, 22 fichiers `static/js/*.js`, `node --check` vert partout, suite 1089/1089. **Lint A27 TERMINÉ 2026-10-01 : `eslint static/js` = 0 erreur / 0 warning** (285 → 0, 22 fichiers). 3 familles traitées : (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 retirés + 24 lignes mortes déterministes (suppressions avec assert sur le texte exact : `var self = this` ×8, compteurs jamais lus `restored++`/`resolved++`/`acc`/`today`, `uid()`/`propName()` sans 1 appel, etc.) + `/* exported */` sur les **10 fonctions appelées depuis les attributs HTML** (vérifiées par grep, 1 template chacune) ; (3) **no-undef ×44** = globaux réels déclarés dans `eslint.config.mjs` (`getSvgIcon` = script inline de `base.html`, `TextDecoder` = API navigateur, `Prism` = CDN) + **2 vrais correctifs** : `settings.js` utilisait `typeof toast === 'function'` (guard toujours faux → les toasts timezone/SAML ne s'affichaient JAMAIS) → `window.showToast`, et `_wsInitData = window._wsInitData` (auto-affectation sans effet, global implicite) supprimé. **Reste A27** : `base` 1 523 L structurel (inline par nature, décision assumée) + ~500 L de petits blocs hors cibles. Effort : **L** (fait). - [ ] **A28 — Dette de découpe (god files)** : `api_v2.py` 115 routes / 131 Ko, `dashboard.py` 63 / 116 Ko (27 pages HTMLResponse + 50 JSON + I/O fichiers, 16 `Environment(...)` locaux), `collections.py` 53 / 112 Ko, `board.py` 53 / 93 Ko (page CRUD + `zipfile` + sync Gitea). *Fix : scinder par **concern** (`pages_html`, `files`, sous-modules `api_v2/*`) — mécanique, 0 changement d'URL. Effort : **L**.* - [x] **A29 — Endpoints dupliqués 2-3×** : publish/unpublish existe en 3 endroits (`sharing.py:304/345`, `board.py:1020/1039`, `api_v2.py:1743/1761`) avec slug et auth **différents** ; listing collections ×3 (`/api/v1/collections`, `/db/api`, `/api/v2/collections`) ; `/api/users/me` ×2. *Fix : un `services/publish.py` partagé, les routers déléguent.* — **fait 2026-10-01** : `services/publish.py` (slugify unique, 404 partout, événements) ; les 3 paires publish/unpublish déléguent (sharing + board + v2), board gagne `_require_auth`, les bonus divergents (`share_mode='anyone'` / `is_shared=1`) supprimés — le share dialog reste propriétaire de ces drapeaux ; **byproduct sécurité** : `GET /api/users/me` (v1) et le contexte de `/accounts` faisaient `SELECT *` → `password_hash` exposé → colonnes whitelistées. **Décision** : `/api/users/me` ×2 et listing collections ×3 **restent** — contrats versionnés distincts (session+guest vs Bearer+scope, formes différentes). Effort : **M**. - [x] **A30 — 16 fonctions top-level jamais référencées**, dont `require_scope` (`api_v2_helpers.py:213`, la factory FastAPI qui doit faire les scopes — les handlers font `has_scope(...)` à la main), `validate_upload`, `_get_user_or_redirect`, `_require_user_gitea`, `unsync_block`, `find_referring`… *Fix : câbler `validate_upload` (A22) + `require_scope`, supprimer le reste. Effort : **S**.* @@ -1181,4 +1181,4 @@ Quality DB views, Agent IA Palette → Realtime + E → Puis **A3–A8** (le bloc « fallback admin ») d'un seul tenant, puis **A10** (autoescape) qui débloque A18/A20. *Audit produit le 2026-09-30 · 43 items · aucun code modifié ( ROADMAP seul ).* -→ **A1–A9 corrigés le 2026-09-30** : deps réinstallées (`pyotp`/`webauthn`/`cbor2`), rebinding de `settings` supprimé dans `test_v54.py` → **suite 1016/1016 verts**, cycle committé (`1706ad1`) + tag `v7.3.0` poussé, `.db`/fichiers de test désindexés, `APP_SECRET_KEY` roté dans `.env` (sessions révoquées) · **A3–A8 : 401 sans session sur les routes de compte (mdp actuel exigé), tokens `/api/v1` + `/api/user` sans session → 401, CRUD membres d'espace sous session+role admin, `_require_view`/`_require_edit` sans session → 404/401, création/lecture de page sous session, `/board/api/pages` + `/api/user` sortis du CSRF exempt, seed admin sans mdp en dur (aléatoire ou `FLOWDECK_ADMIN_PASSWORD`). Tests : client connecte par defaut (`_TestSessionAuth`), helper `anon()` sur les 40 tests d'anonymat → suite 1016/1016 + ruff OK, commit `d125eb3` · **A10 : `app/templating.py` (ENV partagé + autoescape `select_autoescape(["html"])`) remplace les 29 instantiations, `|safe` retriés (corps d'issue/commentaires echappes, `sidebar_config` en `|tojson`) → suite 1016/1016, version 7.3.1 · **A11 (traversal avatar) + A18 (vue publique : 404 restricted/private + html.escape)** : `tests/test_audit_p0_fixes.py`, suite 1019/1019, version 7.3.2 · **A12/A13/A15/A17/A22/A23/A24** : garde SSRF par hop, routers automations + /api sous session/Bearer, webhooks admin+URL publique, uploads validés, N+1 en GROUP BY/executemany, 2 doublons de routes supprimés → suite 1025/1025, version 7.3.3 · **A16** : exports + pièces jointes sous session + `can_view_page` → suite 1026/1026, version 7.3.4 · **A19 (partiel)** : 12 préfixes CSRF retirés après scan front (12 préfixes = tous ceux dont les appels portent déjà le header), reste 5 préfixes / 49 fetchs → suite 1026/1026, version 7.3.5 · **A19 terminé** : 46 call sites front équipés, plus aucun préfixe cookie-auth exempté → suite 1026/1026, version 7.3.6 · **A14** : fallback `admin` supprimé de `_current_user_id`/`_current_admin` (401 sans session), `api_base` validé scheme http(s) sans identifiants (hôtes privés acceptés = Ollama localhost, commentaire `ponytail:`) → suite 1027/1027, version 7.3.7 · **A25** : 84 `except Exception: pass` remplacés par `logger.exception(fn)` (19 fichiers, +`logger` manquants), try supprimé sur `materialize_properties` dans `create_collection_v2` et `apply_db_template_v2` (rollback au lieu d'un commit sans schéma), test de rollback · **A21 (partiel)** : `busy_timeout=5000` dans `get_conn()` → suite 1028/1028, version 7.3.8 · **A26/A33/A34/A35/A36/A43** : secret par défaut refusé au boot, rate limit (préfixes + settings + XFF + épurage), `_spawn()` pour les 10 schedulers, OpenAPI 511 chemins + README, 4 deps mortes purgées, 15 `utcnow()` → `now(UTC)` naïf → suite 1028/1028, version 7.3.9. · **A30/A37/A39/A40/A41** : `require_scope` câblé sur 69 sites + 12 fonctions mortes supprimées, CORS sans `*` (origines de `app_base_url` + regex dev/extensions), assets versionnés depuis `VERSION` (source unique), `app.css` -10,2 Ko de règles mortes, htmx = décision « rien » documentée → suite 1031/1031, version 7.4.0. · **A29/A42** : `services/publish.py` partagé (3 routers déléguent, 404 partout, board sous session), fuite `password_hash` corrigée sur `GET /api/users/me` v1 + contexte `/accounts`, `settings.data_dir` remplace les 9 copies d'env, cache Gitea évacue les expirés ; `/users/me` ×2 + collections ×3 = contrats versionnés, on garde ; reste A42 = client httpx partagé → suite 1034/1034, version 7.5.0. · **A31** : transaction par migration (`_apply_one`, rollback tout-ou-rien du DDL) + helper `columns()` remplaçant 25 copies de `PRAGMA table_info` (1 helper au lieu de 3 — les 2 autres seraient mort-nés) → suite 1036/1036, version 7.6.0. · **A20 (partiel)** : CSP nonce par requête — `unsafe-inline` retiré de `script-src`, 38 scripts templates + login constant + 3 scripts Python noncés, meta `htmx-config` pour htmx, `script-src-attr` pour les 74 `onclick=`, CDN chart/leaflet débloqués (déjà cassés avant) → suite 1037/1037, version 7.7.0. · **A21 phase 1** : 352 routes `async def` sans `await` → `def` (threadpool FastAPI, SQLite hors loop, zéro changement de logique — scan corps par corps) ; reste phase 2 = 311 routes avec `await` → `anyio.to_thread.run_sync` par bloc DB → suite 1037/1037, version 7.8.0. · **A21 phase 2a** : `body` JSON → paramètre `Body(default={})` sur les 36 routes api_v2 dont c'était le seul `await` → conversion en `def` → api_v2 à 96/115 hors loop (19 async restantes : fire_event/form/gitea) → suite 1037/1037, version 7.9.0. · **A21 phase 2b** : `run_event_sync()` (asyncio.run dans le worker, événement attendu = déterministe) + les 15 routes json/événements en `def` → api_v2 bouclé à 111/115 (4 async = vrais awaits réseau) ; repo-wide 403 sync / 260 async → suite 1037/1037, version 7.10.0. · **A32 phase 1** : les 4 routers à 0 test couverts (webhooks/notes/sidebar_config/github_routes = 10 smokes, 0 réseau réel, échappement notes vérifié) ; reste quasi nuls library/api/dashboard/api_v2 → suite 1047/1047, version 7.11.0. · **A32 phase 2a** : library 1/10 → 8 routes couvertes ; découverte = 2 routes lisant la table fantôme `local_workspace_items` (500 systématique, 0 ref front) supprimées + `_format_size` mort → suite 1053/1053, version 7.12.0. · **A32 phase 2b** : api.py 3 → 16/22 routes couvertes (board-config, col-mapping, card, collaborators stubbé, frontend-error dédup, mutations checklist vérifiées en base) ; reste 6 routes gitea + dashboard/api_v2 → suite 1059/1059, version 7.13.0. · **A32 phase 2c** : api_v2 scan strict → 5 routes à 0 ref couvertes (formula, rollup, audit-logs avec portail vérifié, webhooks/events, verify-signature signé) → suite 1064/1064, version 7.14.0. · **A32 phase 2d** : dashboard scan strict = 44 routes à 0 ref, 10 couvertes (tags CRUD, page content→rename→trash vérifiés en base, tree HTML, avatar-color restauré, members) → suite 1069/1069, version 7.15.0. · **A32 phase 2e** : +9 routes dashboard (pages comptes sans hash, profile/password A3 403, token fd_+64hex, forge, settings/account 400, select cookie, breadcrumb) → suite 1075/1075, version 7.16.0. · **A32 phase 2f** : +7 routes dashboard centrées A16 (files traversal 403, download/file-content sans fuite, avatar 302 sans réseau, table-data + création de ligne) → suite 1079/1079, version 7.17.0. · **A32 phase 2g** : +13 routes dashboard (gitea-workspace HTML, projects CRUD, cycle items 5 routes, cycle tags d'item 5 routes avec ws dédié) → suite 1083/1083, version 7.18.0. · **A32 phase 2h → dashboard bloqué** : members (cycle complet + quirk tuple), upload-folder (validations seules), convert-to-database (vérifié en base, ordre FK) ; les 44 routes à 0 ref sont toutes exercées (faux positifs f-string rapprochés) → suite 1086/1086, version 7.19.0. · **A32 TERMINÉ** : 6 routes Gitea stubbées (canevas mutable, carte board, HTML `?format=html`, 404) + **bug prod `card_detail.html`/`fd_icon` corrigé** (500 garanti avant) → suite 1089/1089, version 7.20.0. · **A27 phase 1** : 7 templates sans Jinja → 9 fichiers static/js (4 243 L, -30 % du inline, node --check vert), eslint installé globalement → `eslint static/js` 0 erreur/120 warnings ; reste = blocs interpolés Jinja (~9 661 L) → suite 1089/1089, version 7.21.0. · **A27 phase 2a** : éditeur 2 516 L extrait via `#page-data` (PD.*), dérivé `is_shared` hoisté, 8 tests adaptés (source → static js / parsing JSON) → suite 1089/1089, version 7.22.0. · **A27 phase 2b** : local_workspace 2031 + settings 1093 + realtime 531 + board 146 extraits (recette config JSON, tojson au lieu d'interpolation JS) → suite 1089/1089, version 7.23.0. · **A27 phase 2c → extraction TERMINÉE** : database_table 1314 L (config JSON null-vs-objet, 2 branches Jinja remplacées par `DB_CONFIG`), bilan **11 874 L extraites, inline -85 % (13 904 → 2 022)** ; reste base 1523 structurel + 500 L de petits blocs + 285 warnings eslint → suite 1089/1089, version 7.24.0. +→ **A1–A9 corrigés le 2026-09-30** : deps réinstallées (`pyotp`/`webauthn`/`cbor2`), rebinding de `settings` supprimé dans `test_v54.py` → **suite 1016/1016 verts**, cycle committé (`1706ad1`) + tag `v7.3.0` poussé, `.db`/fichiers de test désindexés, `APP_SECRET_KEY` roté dans `.env` (sessions révoquées) · **A3–A8 : 401 sans session sur les routes de compte (mdp actuel exigé), tokens `/api/v1` + `/api/user` sans session → 401, CRUD membres d'espace sous session+role admin, `_require_view`/`_require_edit` sans session → 404/401, création/lecture de page sous session, `/board/api/pages` + `/api/user` sortis du CSRF exempt, seed admin sans mdp en dur (aléatoire ou `FLOWDECK_ADMIN_PASSWORD`). Tests : client connecte par defaut (`_TestSessionAuth`), helper `anon()` sur les 40 tests d'anonymat → suite 1016/1016 + ruff OK, commit `d125eb3` · **A10 : `app/templating.py` (ENV partagé + autoescape `select_autoescape(["html"])`) remplace les 29 instantiations, `|safe` retriés (corps d'issue/commentaires echappes, `sidebar_config` en `|tojson`) → suite 1016/1016, version 7.3.1 · **A11 (traversal avatar) + A18 (vue publique : 404 restricted/private + html.escape)** : `tests/test_audit_p0_fixes.py`, suite 1019/1019, version 7.3.2 · **A12/A13/A15/A17/A22/A23/A24** : garde SSRF par hop, routers automations + /api sous session/Bearer, webhooks admin+URL publique, uploads validés, N+1 en GROUP BY/executemany, 2 doublons de routes supprimés → suite 1025/1025, version 7.3.3 · **A16** : exports + pièces jointes sous session + `can_view_page` → suite 1026/1026, version 7.3.4 · **A19 (partiel)** : 12 préfixes CSRF retirés après scan front (12 préfixes = tous ceux dont les appels portent déjà le header), reste 5 préfixes / 49 fetchs → suite 1026/1026, version 7.3.5 · **A19 terminé** : 46 call sites front équipés, plus aucun préfixe cookie-auth exempté → suite 1026/1026, version 7.3.6 · **A14** : fallback `admin` supprimé de `_current_user_id`/`_current_admin` (401 sans session), `api_base` validé scheme http(s) sans identifiants (hôtes privés acceptés = Ollama localhost, commentaire `ponytail:`) → suite 1027/1027, version 7.3.7 · **A25** : 84 `except Exception: pass` remplacés par `logger.exception(fn)` (19 fichiers, +`logger` manquants), try supprimé sur `materialize_properties` dans `create_collection_v2` et `apply_db_template_v2` (rollback au lieu d'un commit sans schéma), test de rollback · **A21 (partiel)** : `busy_timeout=5000` dans `get_conn()` → suite 1028/1028, version 7.3.8 · **A26/A33/A34/A35/A36/A43** : secret par défaut refusé au boot, rate limit (préfixes + settings + XFF + épurage), `_spawn()` pour les 10 schedulers, OpenAPI 511 chemins + README, 4 deps mortes purgées, 15 `utcnow()` → `now(UTC)` naïf → suite 1028/1028, version 7.3.9. · **A30/A37/A39/A40/A41** : `require_scope` câblé sur 69 sites + 12 fonctions mortes supprimées, CORS sans `*` (origines de `app_base_url` + regex dev/extensions), assets versionnés depuis `VERSION` (source unique), `app.css` -10,2 Ko de règles mortes, htmx = décision « rien » documentée → suite 1031/1031, version 7.4.0. · **A29/A42** : `services/publish.py` partagé (3 routers déléguent, 404 partout, board sous session), fuite `password_hash` corrigée sur `GET /api/users/me` v1 + contexte `/accounts`, `settings.data_dir` remplace les 9 copies d'env, cache Gitea évacue les expirés ; `/users/me` ×2 + collections ×3 = contrats versionnés, on garde ; reste A42 = client httpx partagé → suite 1034/1034, version 7.5.0. · **A31** : transaction par migration (`_apply_one`, rollback tout-ou-rien du DDL) + helper `columns()` remplaçant 25 copies de `PRAGMA table_info` (1 helper au lieu de 3 — les 2 autres seraient mort-nés) → suite 1036/1036, version 7.6.0. · **A20 (partiel)** : CSP nonce par requête — `unsafe-inline` retiré de `script-src`, 38 scripts templates + login constant + 3 scripts Python noncés, meta `htmx-config` pour htmx, `script-src-attr` pour les 74 `onclick=`, CDN chart/leaflet débloqués (déjà cassés avant) → suite 1037/1037, version 7.7.0. · **A21 phase 1** : 352 routes `async def` sans `await` → `def` (threadpool FastAPI, SQLite hors loop, zéro changement de logique — scan corps par corps) ; reste phase 2 = 311 routes avec `await` → `anyio.to_thread.run_sync` par bloc DB → suite 1037/1037, version 7.8.0. · **A21 phase 2a** : `body` JSON → paramètre `Body(default={})` sur les 36 routes api_v2 dont c'était le seul `await` → conversion en `def` → api_v2 à 96/115 hors loop (19 async restantes : fire_event/form/gitea) → suite 1037/1037, version 7.9.0. · **A21 phase 2b** : `run_event_sync()` (asyncio.run dans le worker, événement attendu = déterministe) + les 15 routes json/événements en `def` → api_v2 bouclé à 111/115 (4 async = vrais awaits réseau) ; repo-wide 403 sync / 260 async → suite 1037/1037, version 7.10.0. · **A32 phase 1** : les 4 routers à 0 test couverts (webhooks/notes/sidebar_config/github_routes = 10 smokes, 0 réseau réel, échappement notes vérifié) ; reste quasi nuls library/api/dashboard/api_v2 → suite 1047/1047, version 7.11.0. · **A32 phase 2a** : library 1/10 → 8 routes couvertes ; découverte = 2 routes lisant la table fantôme `local_workspace_items` (500 systématique, 0 ref front) supprimées + `_format_size` mort → suite 1053/1053, version 7.12.0. · **A32 phase 2b** : api.py 3 → 16/22 routes couvertes (board-config, col-mapping, card, collaborators stubbé, frontend-error dédup, mutations checklist vérifiées en base) ; reste 6 routes gitea + dashboard/api_v2 → suite 1059/1059, version 7.13.0. · **A32 phase 2c** : api_v2 scan strict → 5 routes à 0 ref couvertes (formula, rollup, audit-logs avec portail vérifié, webhooks/events, verify-signature signé) → suite 1064/1064, version 7.14.0. · **A32 phase 2d** : dashboard scan strict = 44 routes à 0 ref, 10 couvertes (tags CRUD, page content→rename→trash vérifiés en base, tree HTML, avatar-color restauré, members) → suite 1069/1069, version 7.15.0. · **A32 phase 2e** : +9 routes dashboard (pages comptes sans hash, profile/password A3 403, token fd_+64hex, forge, settings/account 400, select cookie, breadcrumb) → suite 1075/1075, version 7.16.0. · **A32 phase 2f** : +7 routes dashboard centrées A16 (files traversal 403, download/file-content sans fuite, avatar 302 sans réseau, table-data + création de ligne) → suite 1079/1079, version 7.17.0. · **A32 phase 2g** : +13 routes dashboard (gitea-workspace HTML, projects CRUD, cycle items 5 routes, cycle tags d'item 5 routes avec ws dédié) → suite 1083/1083, version 7.18.0. · **A32 phase 2h → dashboard bloqué** : members (cycle complet + quirk tuple), upload-folder (validations seules), convert-to-database (vérifié en base, ordre FK) ; les 44 routes à 0 ref sont toutes exercées (faux positifs f-string rapprochés) → suite 1086/1086, version 7.19.0. · **A32 TERMINÉ** : 6 routes Gitea stubbées (canevas mutable, carte board, HTML `?format=html`, 404) + **bug prod `card_detail.html`/`fd_icon` corrigé** (500 garanti avant) → suite 1089/1089, version 7.20.0. · **A27 phase 1** : 7 templates sans Jinja → 9 fichiers static/js (4 243 L, -30 % du inline, node --check vert), eslint installé globalement → `eslint static/js` 0 erreur/120 warnings ; reste = blocs interpolés Jinja (~9 661 L) → suite 1089/1089, version 7.21.0. · **A27 phase 2a** : éditeur 2 516 L extrait via `#page-data` (PD.*), dérivé `is_shared` hoisté, 8 tests adaptés (source → static js / parsing JSON) → suite 1089/1089, version 7.22.0. · **A27 phase 2b** : local_workspace 2031 + settings 1093 + realtime 531 + board 146 extraits (recette config JSON, tojson au lieu d'interpolation JS) → suite 1089/1089, version 7.23.0. · **A27 phase 2c → extraction TERMINÉE** : database_table 1314 L (config JSON null-vs-objet, 2 branches Jinja remplacées par `DB_CONFIG`), bilan **11 874 L extraites, inline -85 % (13 904 → 2 022)** → suite 1089/1089, version 7.24.0. · **A27 lint TERMINÉ** : eslint 285 warnings → **0/0** (catch vides → binding optionnel, 24 lignes mortes, 10 fonctions `/* exported */` appelées par le HTML, 3 globaux réels en config) + 2 vrais correctifs (toasts settings jamais affichés → `window.showToast`, `_wsInitData` sans effet supprimé) → suite 1089/1089, version 7.25.0. diff --git a/VERSION b/VERSION index 0783219..bc34d81 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.24.0 +7.25.0 diff --git a/WORKLOAD.md b/WORKLOAD.md index 7ad1a5c..1688866 100644 --- a/WORKLOAD.md +++ b/WORKLOAD.md @@ -1,6 +1,6 @@ # WORKLOAD — FlowDeck Notion Clone -> **Début**: 2026-07-08 | **Version**: v7.24.0 (audit — A27 extraction TERMINÉE : 11 874 L, -85 % inline) | **Statut**: EN COURS 🔄 +> **Début**: 2026-07-08 | **Version**: v7.25.0 (audit — A27 CLOSED : eslint 0/0, 285 warnings nettoyés) | **Statut**: EN COURS 🔄 > **Cible**: parité Notion + intégration forge · **Follow-ups v7.3 livrés**: sidebar teamspaces, notif `page.updated`, charts `number` + dashboards multi-DB, unfurl forge, UI Settings → Audit — voir `ROADMAP.md § v7.3.0` ## Avancement Global diff --git a/app/main.py b/app/main.py index bd81955..7846379 100644 --- a/app/main.py +++ b/app/main.py @@ -185,7 +185,7 @@ async def lifespan(_app: FastAPI): app = FastAPI( title="FlowDeck", - version="7.24.0", + version="7.25.0", docs_url="/docs", redoc_url="/redoc", lifespan=lifespan, diff --git a/docs/openapi-v2.json b/docs/openapi-v2.json index 3ea86d8..ee6b666 100644 --- a/docs/openapi-v2.json +++ b/docs/openapi-v2.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "FlowDeck", - "version": "7.24.0" + "version": "7.25.0" }, "paths": { "/auth/register": { diff --git a/eslint.config.mjs b/eslint.config.mjs index d925390..a9cc816 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -40,6 +40,9 @@ const browserGlobals = { Alpine: "readonly", htmx: "readonly", Sortable: "readonly", // Globals exposed on window by app.js openModal: "readonly", closeModal: "readonly", + // getSvgIcon : script inline de base.html (nonce) ; Prism : CDN des vues ; + // TextDecoder : API navigateur (ES2015) + getSvgIcon: "readonly", Prism: "readonly", TextDecoder: "readonly", }; export default [ diff --git a/static/js/_ctx_menu.js b/static/js/_ctx_menu.js index 9e19405..154a13c 100644 --- a/static/js/_ctx_menu.js +++ b/static/js/_ctx_menu.js @@ -61,7 +61,7 @@ self._place(); var el = document.querySelector('.fd-ctx-menu'); if (el && window.ResizeObserver) { - if (self._ro) { try { self._ro.disconnect(); } catch (e) {} } + if (self._ro) { try { self._ro.disconnect(); } catch { /* volontaire */ } } self._ro = new ResizeObserver(function () { self._place(); }); self._ro.observe(el); } @@ -95,7 +95,7 @@ this.maxH = Math.max(120, vh - ny - 8); }, close() { - if (this._ro) { try { this._ro.disconnect(); } catch (e) {} this._ro = null; } + if (this._ro) { try { this._ro.disconnect(); } catch { /* volontaire */ } this._ro = null; } this.open = false; this.node = null; this.handlers = {}; diff --git a/static/js/_icon_picker_2.js b/static/js/_icon_picker_2.js index e6c17e5..eb5a300 100644 --- a/static/js/_icon_picker_2.js +++ b/static/js/_icon_picker_2.js @@ -126,7 +126,7 @@ remove() { var fn = this.onRemove || this.onPick; - if (fn) { try { fn(''); } catch (e) {} } + if (fn) { try { fn(''); } catch { /* volontaire */ } } this.close(); }, @@ -143,7 +143,7 @@ loadRecent() { try { this.recent = JSON.parse(localStorage.getItem('fd_icon_recent') || '[]'); } - catch (e) { this.recent = []; } + catch { this.recent = []; } }, pushRecent(e) { try { @@ -151,7 +151,7 @@ r.unshift(e); this.recent = r.slice(0, 32); localStorage.setItem('fd_icon_recent', JSON.stringify(this.recent)); - } catch (err) {} + } catch { /* volontaire */ } }, async loadCustom() { @@ -160,7 +160,7 @@ var d = await r.json(); this.custom = (d && d.emojis) || []; this.customLoaded = true; - } catch (e) { this.custom = []; } + } catch { this.custom = []; } }, openCustomModal() { @@ -196,7 +196,7 @@ this.customModal = false; this.pick(d.emoji.url); } - } catch (e) { + } catch { if (window.showToast) window.showToast('Emoji upload failed', 'error'); } this.customBusy = false; @@ -206,7 +206,7 @@ var csrf = (document.cookie.match(/csrf_token=([^;]+)/) || [])[1] || ''; await fetch('/api/custom-emojis/' + id, { method: 'DELETE', headers: { 'X-CSRF-Token': csrf }, credentials: 'same-origin' }); this.custom = this.custom.filter(function (e) { return e.id !== id; }); - } catch (e) {} + } catch { /* volontaire */ } } }); }); diff --git a/static/js/agent_panel_2.js b/static/js/agent_panel_2.js index 524c17f..b4c08a1 100644 --- a/static/js/agent_panel_2.js +++ b/static/js/agent_panel_2.js @@ -430,7 +430,7 @@ if(!lastAsst) return; var steps = acts.map(function(a){ var res = null; - try{ res = a.result_json ? JSON.parse(a.result_json) : {}; }catch(e){ res = {}; } + try{ res = a.result_json ? JSON.parse(a.result_json) : {}; }catch{ res = {}; } var msg = (res && res.message) ? res.message : ''; return agentPanel._actionStep({ tool: a.tool_name, status: a.status, target_id: a.target_id, @@ -672,7 +672,7 @@ r.insertNode(br); r.setStartAfter(br); r.collapse(true); this.input = this.composerText(); - }catch(e){} + }catch { /* volontaire */ } this.resizeInput(); }, _caretQuery(){ @@ -687,7 +687,7 @@ if(i > 0 && s[i-1] === '@' && (i === 1 || /\s/.test(s[i-2]))){ return {node: node, at: i-1, raw: s.slice(i, off)}; } - }catch(e){} + }catch { /* volontaire */ } return null; }, _expandToAt(range){ @@ -727,14 +727,14 @@ useRange.setStartAfter(span); useRange.collapse(true); useRange.insertNode(sp); useRange.setStart(sp, 1); useRange.setEnd(sp, 1); - try{ var s2 = window.getSelection(); if(s2){ s2.removeAllRanges(); s2.addRange(useRange); } }catch(e2){} + try{ var s2 = window.getSelection(); if(s2){ s2.removeAllRanges(); s2.addRange(useRange); } }catch { /* volontaire */ } } else { if(el.textContent) el.appendChild(document.createTextNode(' ')); el.appendChild(span); el.appendChild(document.createTextNode(' ')); - try{ var r2 = document.createRange(); r2.selectNodeContents(el); r2.collapse(false); var s3 = window.getSelection(); if(s3){ s3.removeAllRanges(); s3.addRange(r2); } }catch(e3){} + try{ var r2 = document.createRange(); r2.selectNodeContents(el); r2.collapse(false); var s3 = window.getSelection(); if(s3){ s3.removeAllRanges(); s3.addRange(r2); } }catch { /* volontaire */ } } - }catch(e){} + }catch { /* volontaire */ } this.input = this.composerText(); this.resizeInput(); this._emptyCheck(); @@ -753,7 +753,7 @@ this._insertToken(item, r); return; } - }catch(e){} + }catch { /* volontaire */ } } this._insertToken(item); }, @@ -803,12 +803,12 @@ onPaste(e){ e.preventDefault(); var text = ''; - try{ if(e.clipboardData) text = e.clipboardData.getData('text/plain') || ''; }catch(err){ } + try{ if(e.clipboardData) text = e.clipboardData.getData('text/plain') || ''; }catch { /* volontaire */ } if(!text) return; try{ var sel = window.getSelection(); if(sel && sel.rangeCount){ sel.getRangeAt(0).deleteContents(); document.execCommand('insertText', false, text); } - }catch(err){} + }catch { /* volontaire */ } this.input = this.composerText(); this.resizeInput(); }, @@ -889,7 +889,6 @@ return null; }, useSuggestion(c){ - var self = this; if(!c || c._sep) return; if(c._admin){ this.setComposerText(c.cmd.replace(/\s*<[^>]*>\s*$/, ' ')); @@ -926,7 +925,6 @@ this.scrollBottom(); }, handleCommand(text){ - var self = this; var parts = text.replace(/^\/+/, '').trim().split(/\s+/); var cmd = (parts.shift() || '').toLowerCase(); var arg = parts.join(' ').trim(); @@ -1164,7 +1162,7 @@ if(tok){ e.preventDefault(); this._removeTokenDom(tok); return; } } } - }catch(err){} + }catch { /* volontaire */ } } if(key === 'Escape'){ if(this.mentionOpen){ e.preventDefault(); e.stopPropagation(); this.mentionOpen = false; this._mentionFrom = null; this._atNode = null; return; } @@ -1219,7 +1217,6 @@ // Render agent markdown responses (headings, tables, code, lists, …) to // HTML so the panel displays content like Notion (no raw `##` / `**`). renderMarkdown(md){ - var self = this; function escHtml(s){ return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>'); } function inline(t){ t = escHtml(t); @@ -1261,7 +1258,6 @@ var t = raw.trim(); // Fenced code block if(t.startsWith('```')){ - var lang = t.substring(3).trim(); var code = []; i++; while(i < lines.length && !lines[i].trim().startsWith('```')){ @@ -1351,7 +1347,7 @@ var ok = document.execCommand('copy'); document.body.removeChild(ta); self.toast(ok ? 'Réponse copiée.' : 'Copie impossible.', !ok); - }catch(err){ self.toast('Copie impossible.', true); } + }catch{ self.toast('Copie impossible.', true); } } if(navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(function(){ self.toast('Réponse copiée.'); }).catch(function(){ fallback(); }); @@ -1418,7 +1414,7 @@ if(ment.length){ text = 'Analyse le contexte référencé.'; } else if(!hasSkills){ return; } } - try { await self.ensureConversation(); } catch(e){ return; } + try { await self.ensureConversation(); } catch{ return; } var rawText = text; // La requête est composée EN ARRIÈRE-PLAN (hors de la boîte d'édition) : // contexte1 + contexte2 + skill-1 + skill-2 + texte. @@ -1505,7 +1501,7 @@ fph.steps = runSteps.slice(); fph.stepsOpen = false; fph.model = self.autoLabel || ''; - if(window.__fdInsertAI){ try{ var h=window.__fdInsertAI; window.__fdInsertAI=null; h(ev.content||''); }catch(e){} } + if(window.__fdInsertAI){ try{ var h=window.__fdInsertAI; window.__fdInsertAI=null; h(ev.content||''); }catch { /* volontaire */ } } } else if(ev.type==='notice'){ self.notice = ev.message; } else if(ev.type==='error'){ @@ -1515,7 +1511,7 @@ eph.steps = runSteps.slice(); self.errorMsg = ev.message; } - }catch(e){} + }catch { /* volontaire */ } }); self.scrollBottom(); return read(); @@ -1631,7 +1627,7 @@ var out = title ? ('Titre du document : ' + title) : 'Document ouvert'; if(text) out += '\n\n' + text; return out.slice(0, 9000); - }catch(e){ return ''; } + }catch{ return ''; } }; if(!window.fdGetPageContext) window.fdGetPageContext = window.fdReadPageContext; @@ -1644,7 +1640,7 @@ try{ var m = (window.location.pathname || '').match(new RegExp('^\\/?' + prefix + '\\/(\\d+)')); return m ? m[1] : null; - }catch(e){ return null; } + }catch{ return null; } } function labelFromTitle(){ try{ @@ -1652,7 +1648,7 @@ t = t.replace(/^\s*FlowDeck\s*-\s*/i, ''); t = t.replace(/\s*[-|]\s*FlowDeck\s*$/i, ''); if(t && t.toLowerCase() !== 'home' && t.toLowerCase() !== 'flowdeck') return t; - }catch(e){} + }catch { /* volontaire */ } return ''; } function docContext(){ @@ -1671,7 +1667,7 @@ refType: pid ? 'document' : null }; } - }catch(e){} + }catch { /* volontaire */ } return null; } function marker(){ @@ -1686,7 +1682,7 @@ var lbl = b.getAttribute('data-fd-context'); return {kind:'section', icon:'📍', label:lbl, text:'', key:'marker'}; } - }catch(e){} + }catch { /* volontaire */ } return null; } function byPath(){ @@ -1777,7 +1773,7 @@ if(E.autoSave) E.autoSave(); if(E.render) E.render(); return true; - }catch(e){ return false; } + }catch{ return false; } }; // Backward-compatible detection hooks (banner + Ask AI) diff --git a/static/js/board.js b/static/js/board.js index d3f9397..b3a826a 100644 --- a/static/js/board.js +++ b/static/js/board.js @@ -1,4 +1,5 @@ -const BD=(()=>{try{const el=document.getElementById('bd-config');return el?JSON.parse(el.textContent):{}}catch(e){return {}}})(); +/* exported setActiveTab, kanbanBoard, filterSystem, sortSystem, newIssueForm, showNewIssue -- appeles depuis les attributs HTML des templates */ +const BD=(()=>{try{const el=document.getElementById('bd-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})(); var owner = BD.owner; var repo = BD.repo; diff --git a/static/js/database_table.js b/static/js/database_table.js index f34087a..420d4e7 100644 --- a/static/js/database_table.js +++ b/static/js/database_table.js @@ -1,4 +1,4 @@ -const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v=el?JSON.parse(el.textContent):null;return v===undefined?null:v}catch(e){return null}})(); +const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v=el?JSON.parse(el.textContent):null;return v===undefined?null:v}catch{return null}})(); (function() { 'use strict'; @@ -47,7 +47,7 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v= var PALETTE = {gray:'#8b8b8b',brown:'#9f6b53',orange:'#d9730d',yellow:'#cb912f',green:'#448361',blue:'#337ea9',purple:'#9065b0',pink:'#c14c8a',red:'#d44c47'}; function escHtml(s) { return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } - function parseJSON(s, d) { try { return typeof s === 'string' ? JSON.parse(s) : (s || d); } catch(e) { return d; } } + function parseJSON(s, d) { try { return typeof s === 'string' ? JSON.parse(s) : (s || d); } catch { return d; } } function parseOpts(prop) { var o = parseJSON(prop.options_json, []); if (!Array.isArray(o)) o = []; @@ -57,7 +57,6 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v= var found = parseOpts(prop).filter(function(o){ return o.name === name; })[0]; return (found && PALETTE[found.color]) || '#8b8b8b'; } - function uid() { return 'dbx' + Math.random().toString(36).slice(2,9); } function getCsrf() { var m = document.cookie.match(/csrf_token=([^;]+)/); return m ? m[1] : ''; } function toast(msg, kind) { if (typeof window.showToast === 'function') window.showToast(msg, kind); @@ -167,7 +166,6 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v= if (ref === undefined || ref === null || ref === '') return null; return state.properties.filter(function(p){ return String(p.id) === String(ref) || p.name === ref; })[0] || null; } - function propName(ref) { var p = findProp(ref); return p ? p.name : ''; } function firstPropOfTypes(types) { return state.properties.filter(function(p){ return types.indexOf(p.prop_type) >= 0; })[0] || null; } @@ -321,7 +319,7 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v= h += ''; props.forEach(function(prop){ var w = state.colWidths[prop.id] || 200; - var groupIdx = 0; var acc = 0; + var groupIdx = 0; groups.forEach(function(g, gi){ if (g.props.indexOf(prop) >= 0) groupIdx = gi; }); var collapsed = hasGroups && state.collapsedGroups[groupIdx]; h += ''; @@ -523,7 +521,6 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v= if (!dp) return '
Add a Date property to use the calendar view.
'; var mode = calMode(); var cur = state.calCursor; - var today = new Date(); var h = '
'; h += '
'; var title; diff --git a/static/js/gitea_workspace.js b/static/js/gitea_workspace.js index 0a7e014..6263255 100644 --- a/static/js/gitea_workspace.js +++ b/static/js/gitea_workspace.js @@ -63,7 +63,7 @@ document.addEventListener('alpine:init', () => { var d = await r.json(); this.treeItems = d.tree || []; this._sortTree(); - } catch(e) { this.treeItems = []; this.sortedTreeItems = []; } + } catch { this.treeItems = []; this.sortedTreeItems = []; } finally { this.treeLoading = false; } }, @@ -267,7 +267,7 @@ document.addEventListener('alpine:init', () => { ul.appendChild(li); }); el.appendChild(ul); - } catch(e) {} + } catch { /* volontaire */ } }, async openFile(path, sha) { @@ -286,7 +286,7 @@ document.addEventListener('alpine:init', () => { } else { this.fileContent = '[Error loading file]'; } - } catch(e) { + } catch { this.fileContent = '[Error loading file]'; } this.fileLoading = false; @@ -343,7 +343,7 @@ document.addEventListener('alpine:init', () => { var d = await r.json(); window.showToast('Failed to save: ' + (d.error || 'Unknown error'), 'error'); } - } catch(e) { window.showToast('Network error', 'error'); } + } catch { window.showToast('Network error', 'error'); } }, async deleteCurrentFile() { @@ -358,7 +358,7 @@ document.addEventListener('alpine:init', () => { this.filePath = ''; await this.refreshTree(); } - } catch(e) {} + } catch { /* volontaire */ } }, async refreshTree() { @@ -380,7 +380,7 @@ document.addEventListener('alpine:init', () => { try { var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/private-pages'); if (r.ok) { var d = await r.json(); this.privatePages = d.pages || []; } - } catch(e) {} + } catch { /* volontaire */ } }, newPrivate() { @@ -398,7 +398,7 @@ document.addEventListener('alpine:init', () => { this.editingPrivateTitle = d.page.title; this.editingPrivateContent = d.page.content; } - } catch(e) {} + } catch { /* volontaire */ } }, async savePrivate() { @@ -419,7 +419,7 @@ document.addEventListener('alpine:init', () => { this.editingPrivateContent = ''; await this.loadPrivatePages(); } - } catch(e) {} + } catch { /* volontaire */ } }, // Create new file @@ -477,7 +477,7 @@ document.addEventListener('alpine:init', () => { method: 'DELETE', headers: {'X-CSRF-Token': this.getCsrfToken ? this.getCsrfToken() : ''} }); if (r.ok) await this.loadPrivatePages(); - } catch(e) {} + } catch { /* volontaire */ } }, getCsrfToken() { diff --git a/static/js/import.js b/static/js/import.js index 7da2186..26ed1f9 100644 --- a/static/js/import.js +++ b/static/js/import.js @@ -1,3 +1,4 @@ +/* exported importWizard -- appeles depuis les attributs HTML des templates */ function importWizard() { return { @@ -25,11 +26,11 @@ function importWizard() { try { const r = await fetch('/api/import/sources'); this.sources = (await r.json()).sources || []; - } catch(e) {} + } catch { /* volontaire */ } try { const c = await fetch('/api/csrf-token'); this.csrf = (await c.json()).csrf_token; - } catch(e) {} + } catch { /* volontaire */ } }, humanSize(n) { return n > 1048576 ? (n/1048576).toFixed(1)+' Mo' : Math.max(1, Math.round(n/1024))+' Ko'; }, addFiles(list) { @@ -61,7 +62,7 @@ function importWizard() { this.preview = merged; this.mapping = {}; (merged ? merged.pages : []).forEach(p => (p.schema||[]).forEach(c => { this.mapping[c.name]=c.type; })); - } catch(e) { this.error = 'Erreur réseau'; } + } catch { this.error = 'Erreur réseau'; } finally { this.busy = false; setTimeout(()=>{this.progress=0;},800); } }, async importOne(f) { @@ -82,7 +83,7 @@ function importWizard() { if (!r.ok) { f.status='error'; f.error=d.detail||'Erreur'; return null; } f.status = (d.status==='partial' ? 'partial' : 'done'); return d; - } catch(e) { f.status='error'; f.error='Erreur réseau'; return null; } + } catch { f.status='error'; f.error='Erreur réseau'; return null; } }, async doImport() { this.busy = true; this.error=''; this.report=null; this.progress=0; @@ -115,7 +116,7 @@ function importWizard() { const d = await r.json(); if (!r.ok) { this.urlError = d.detail || 'Erreur'; return; } this.report = d; this.progress = 100; - } catch(e) { this.urlError = 'Erreur réseau'; } + } catch { this.urlError = 'Erreur réseau'; } finally { this.busy = false; } }, async doForge(kind) { @@ -132,7 +133,7 @@ function importWizard() { const d = await r.json(); if (!r.ok) { this.forgeError = d.detail || 'Erreur'; return; } this.report = d; this.progress = 100; - } catch(e) { this.forgeError = 'Erreur réseau'; } + } catch { this.forgeError = 'Erreur réseau'; } finally { this.busy = false; } }, async pollJob(jobId, f) { diff --git a/static/js/library.js b/static/js/library.js index 4668a01..38d184f 100644 --- a/static/js/library.js +++ b/static/js/library.js @@ -1,3 +1,4 @@ +/* exported libraryPage -- appeles depuis les attributs HTML des templates */ function libraryPage() { return { @@ -298,7 +299,7 @@ function libraryPage() { } else { if (window.showToast) window.showToast('Not a text file', 'error'); } - } catch (e) { + } catch { if (window.showToast) window.showToast('Copy failed', 'error'); } }, @@ -389,7 +390,7 @@ function libraryPage() { headers: {'Content-Type':'application/json', 'X-CSRF-Token': csrf}, body: JSON.stringify({ parent_id: parentId || 0 }) }); - } catch(e) {} + } catch { /* volontaire */ } } this.clearSelection(); this.loadTab(); @@ -537,7 +538,6 @@ function libraryPage() { doSort() { var s = this.sortBy; if (!s) return; - var self = this; function sortNodes(nodes) { if (!nodes) return; nodes.sort(function(a,b){ @@ -574,7 +574,7 @@ function libraryPage() { item.children = d.items || []; // Mark the tree for depth calculation this._markChildren(item.children, (item.depth || 0) + 1); - } catch(e) { + } catch { item.children = []; } } @@ -701,7 +701,7 @@ function libraryPage() { item.page_icon = icon; this._renderTable(); if (window.showToast) window.showToast('Icon updated', 'success'); - } catch(e) { + } catch { if (window.showToast) window.showToast('Failed to update icon', 'error'); } }, @@ -712,7 +712,7 @@ function libraryPage() { var r = await fetch('/api/settings/tags/all'); var d = await r.json(); this.workspaceTags = d.tags || []; - } catch(e) { this.workspaceTags = []; } + } catch { this.workspaceTags = []; } }, toggleTagFilter(tagName) { @@ -771,7 +771,7 @@ function libraryPage() { this._ctxRefreshAvail(); this._recomputeFlatItems(); } - } catch(e) {} + } catch { /* volontaire */ } }, async toggleFavoriteItem(item) { @@ -809,7 +809,7 @@ function libraryPage() { this.loadTab(); this._syncSidebar(); if (window.showToast) window.showToast('Page duplicated', 'success'); - } catch(e) { + } catch { if (window.showToast) window.showToast('Failed to duplicate page', 'error'); } }, @@ -828,7 +828,7 @@ function libraryPage() { var all = (d.items || []).filter(function(it) { return ids.indexOf(it.id) === -1; }); this._moveAllCandidates = all; this.moveCandidates = all.slice(0, 30); - } catch(e) { + } catch { this._moveAllCandidates = []; this.moveCandidates = []; } @@ -881,7 +881,7 @@ function libraryPage() { if (r.ok) { item.title = newName; } - } catch(e) {} + } catch { /* volontaire */ } this._renderTable(); this._syncSidebar(); }, @@ -908,7 +908,7 @@ function libraryPage() { var cr = await fetch('/api/library/children/' + parentId); var cd = await cr.json(); parent.children = cd.items || []; - } catch(e) {} + } catch { /* volontaire */ } this._recomputeFlatItems(); this._renderTable(); return; @@ -917,7 +917,7 @@ function libraryPage() { if (window.fdNavigate) window.fdNavigate('/pages/' + d.id); else window.location.href = '/pages/' + d.id; } - } catch(e) { + } catch { if (window.showToast) window.showToast('Failed to create page', 'error'); } }, @@ -996,7 +996,7 @@ function libraryPage() { if (days < 7) return days + 'd ago'; if (days < 30) return Math.floor(days/7) + 'w ago'; return dt.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}); - } catch(e) { return d; } + } catch { return d; } } }; } @@ -1029,11 +1029,11 @@ function libraryPage() { if (el && el.__x) { el.__x.$data.peekItem = null; } } dragging = false; - try { localStorage.setItem('fd_peek_width', panel.style.width); } catch(ex) {} + try { localStorage.setItem('fd_peek_width', panel.style.width); } catch { /* volontaire */ } handle.releasePointerCapture(e.pointerId); }); try { var saved = localStorage.getItem('fd_peek_width'); if (saved) panel.style.width = saved; - } catch(ex) {} + } catch { /* volontaire */ } })(); diff --git a/static/js/local_workspace.js b/static/js/local_workspace.js index e24743f..2783200 100644 --- a/static/js/local_workspace.js +++ b/static/js/local_workspace.js @@ -1,4 +1,4 @@ -const LW=(()=>{try{const el=document.getElementById('lw-config');return el?JSON.parse(el.textContent):{}}catch(e){return {}}})(); +const LW=(()=>{try{const el=document.getElementById('lw-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})(); console.log('FLOWDECK v2.4.6 — IIFE running, about to set _wsInitData'); window._wsInitData = (function() { @@ -17,7 +17,7 @@ window._wsInitData = (function() { target:null, expanded: (function() { try { return JSON.parse(localStorage.getItem('fd_ws_expanded') || '{}'); } - catch(e) { return {}; } + catch { return {}; } })(), currentFolder: LW.current_folder_id, searchQuery: '', @@ -118,7 +118,7 @@ window._wsInitData = (function() { pendingSyncCount: 0, pinnedIds: (function() { try { return JSON.parse(localStorage.getItem('fd_ws_pinned') || '[]'); } - catch(e) { return []; } + catch { return []; } })(), // Navigation history @@ -223,13 +223,13 @@ window._wsInitData = (function() { window._wsData.closePreviewPanel(); } dragging = false; - try { localStorage.setItem('fd_peek_width', panel.style.width); } catch(ex) {} + try { localStorage.setItem('fd_peek_width', panel.style.width); } catch { /* volontaire */ } handle.releasePointerCapture(e.pointerId); }); try { var saved = localStorage.getItem('fd_peek_width'); if (saved) panel.style.width = saved; - } catch(ex) {} + } catch { /* volontaire */ } }, _recompute() { @@ -252,7 +252,7 @@ window._wsInitData = (function() { if (idx === -1) this.pinnedIds.push(id); else this.pinnedIds.splice(idx, 1); try { localStorage.setItem('fd_ws_pinned', JSON.stringify(this.pinnedIds)); } - catch(e) {} + catch { /* volontaire */ } this._haptic(); }, isPinned(id) { @@ -380,7 +380,6 @@ window._wsInitData = (function() { _getItemPath(id) { // Build folder path by walking up parent chain via tree - var path = []; var found = this._findInTree(this.tree, id); if (!found) return '—'; return '/' + found.path.reverse().join('/'); @@ -515,7 +514,7 @@ window._wsInitData = (function() { } else { preview = '
Empty document
'; } - } catch(e) { + } catch { preview = '
' + this._escHtml(String(content).substring(0, 3000)) + '
'; } @@ -533,7 +532,7 @@ window._wsInitData = (function() { } } body.innerHTML = preview || '
(empty)
'; - } catch(e) { + } catch { body.innerHTML = '
Preview not available
'; } }, @@ -602,7 +601,7 @@ window._wsInitData = (function() { } else { if (window.showToast) window.showToast('Not a text file', 'error'); } - } catch (e) { + } catch { if (window.showToast) window.showToast('Copy failed', 'error'); } }, @@ -634,7 +633,7 @@ window._wsInitData = (function() { if (window.appState && window.appState.refreshFavorites) window.appState.refreshFavorites(); if (window.showToast) window.showToast(node.favorited ? 'Added to Favorites' : 'Removed from Favorites', 'success'); } - } catch(e) {} + } catch { /* volontaire */ } }, async setItemIcon(node, icon) { @@ -652,7 +651,7 @@ window._wsInitData = (function() { this.displayTree = [...this.tree]; this._reloadAfterAction(); if (window.showToast) window.showToast('Icon updated', 'success'); - } catch(e) { + } catch { if (window.showToast) window.showToast('Failed to update icon', 'error'); } }, @@ -741,7 +740,7 @@ window._wsInitData = (function() { this._computeFilteredTableItems(); this._ctxRefreshAvail(); } - } catch(e) {} + } catch { /* volontaire */ } }, _markTree(nodes, depth) { @@ -873,7 +872,7 @@ window._wsInitData = (function() { if (node) { node.name = newName; } this._reloadAfterAction(); } - } catch(e) {} + } catch { /* volontaire */ } }, esc(s) { @@ -1012,7 +1011,7 @@ window._wsInitData = (function() { _saveExpandedState() { try { localStorage.setItem('fd_ws_expanded', JSON.stringify(this.expanded)); } - catch(e) {} + catch { /* volontaire */ } }, _reloadAfterAction() { this._saveExpandedState(); @@ -1051,7 +1050,6 @@ window._wsInitData = (function() { this._saveExpandedState(); if (window.appState && window.appState.refreshSidebarTree) window.appState.refreshSidebarTree(); if (window.FlowDeck && window.FlowDeck.syncPageTitle) window.FlowDeck.syncPageTitle(node.id, n); - var self = this; if (this.folderStack && this.folderStack.length) { this.folderStack.forEach(function(f) { if (String(f.id) === String(node.id)) f.name = n; }); } @@ -1155,7 +1153,6 @@ window._wsInitData = (function() { if (!ids.length || !ids[0]) return; // Restore via API (soft-delete undo) — single or bulk var self = this; - var restored = 0; var total = ids.length; function restoreOne(i) { if (i >= total) { @@ -1163,7 +1160,6 @@ window._wsInitData = (function() { return; } fetch('/api/local-workspace/items/' + ids[i] + '/restore', {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method: 'POST' }) - .then(function(r) { if (r.ok) restored++; }) .finally(function() { restoreOne(i + 1); }); } restoreOne(0); @@ -1274,7 +1270,6 @@ window._wsInitData = (function() { _fetchRetry(url, json) { var maxRetries = 2; var delay = 300; - var self = this; return new Promise(function(resolve, reject) { function attempt(n) { fetch(url).then(function(r) { @@ -1432,7 +1427,7 @@ window._wsInitData = (function() { } return lines.join('\n').substring(0, 500) || '(empty)'; } - } catch(e) {} + } catch { /* volontaire */ } } // Fallback: raw text return String(content).substring(0, 500); @@ -1575,7 +1570,6 @@ window._wsInitData = (function() { // Find parent node id from the onclick attribute or data var li = parentFolder.closest('.ws-tree-item'); if (li) { - var clickEl = li.querySelector('.name.folder'); // Try to extract ID from onclick or the onclick handler // Use data attributes if available, otherwise fall back } @@ -1646,7 +1640,6 @@ window._wsInitData = (function() { var items = ev.dataTransfer.items; if (!items) return; - var self = this; var dtFiles = ev.dataTransfer.files; // Check if any item is a directory (WebKit only) @@ -1700,7 +1693,6 @@ window._wsInitData = (function() { function doUpload() { // Build structure var structure = []; - var files = []; for (var i = 0; i < allItems.length; i++) { structure.push({path: allItems[i].path, type: allItems[i].type}); } @@ -1711,7 +1703,6 @@ window._wsInitData = (function() { if (allItems[i].type === 'file') fileEntries.push(allItems[i]); } - var resolved = 0; var formData = new FormData(); formData.append('structure', JSON.stringify(structure)); if (parentId) formData.append('parent_id', parentId); @@ -1725,10 +1716,8 @@ window._wsInitData = (function() { var fe = fileEntries[idx]; fe.entry.file(function(file) { formData.append('files', file, fe.path); - resolved++; addFile(idx + 1); }, function() { - resolved++; addFile(idx + 1); }); } @@ -1779,7 +1768,6 @@ window._wsInitData = (function() { if (r.ok) { this.uploadStatus = '✓ ' + d.items.length + ' item(s) uploaded'; this.uploadProgress = 100; - var items = d.items || []; setTimeout(function() { self.uploading = false; self._reloadAfterAction(); @@ -1789,7 +1777,7 @@ window._wsInitData = (function() { this.uploadProgress = 0; setTimeout(function() { self.uploading = false; }, 2000); } - } catch(e) { + } catch { this.uploadStatus = '✗ Network error'; this.uploadProgress = 0; setTimeout(function() { self.uploading = false; }, 2000); @@ -1802,7 +1790,7 @@ window._wsInitData = (function() { var r = await fetch('/api/local-workspace/tags', { cache: 'no-store' }); var d = await r.json(); this.workspaceTags = d.tags || []; - } catch(e) { this.workspaceTags = []; } + } catch { this.workspaceTags = []; } }, // Mise à jour OPTIMISTE du compteur d'un tag dans la barre de tags : @@ -1859,7 +1847,7 @@ window._wsInitData = (function() { await this.loadWorkspaceTags(); this._computeFilteredTableItems(); } - } catch(e) {} + } catch { /* volontaire */ } this.tagInputId = null; }, @@ -1876,7 +1864,7 @@ window._wsInitData = (function() { await this.loadWorkspaceTags(); this._computeFilteredTableItems(); } - } catch(e) {} + } catch { /* volontaire */ } }, _updateNodeTags(nodes, itemId, tag, action, removeTagId) { @@ -1983,7 +1971,7 @@ window._wsInitData = (function() { this._collectFolders(d.tree || [], folders, 0); this._moveAllCandidates = folders; this.moveCandidates = folders.slice(0, 50); - } catch(e) { + } catch { this._moveAllCandidates = []; this.moveCandidates = []; } @@ -2014,7 +2002,7 @@ window._wsInitData = (function() { headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'}, body: JSON.stringify({ parent_id: parentId || null }) }); - } catch(e) {} + } catch { /* volontaire */ } } this.clearSelection(); this._reloadAfterAction(); @@ -2026,7 +2014,6 @@ window._wsInitData = (function() { // Injecter toutes les props comme globales pour Alpine (résout les 36 erreurs de scope) var _wsKeys = Object.keys(window._wsInitData); for (var _i = 0; _i < _wsKeys.length; _i++) { - try { window[_wsKeys[_i]] = window._wsInitData[_wsKeys[_i]]; } catch(e) {} + try { window[_wsKeys[_i]] = window._wsInitData[_wsKeys[_i]]; } catch { /* volontaire */ } } -_wsInitData = window._wsInitData; console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(window._wsInitData).length); diff --git a/static/js/page_editor_realtime.js b/static/js/page_editor_realtime.js index 5cca1c1..57c9414 100644 --- a/static/js/page_editor_realtime.js +++ b/static/js/page_editor_realtime.js @@ -1,4 +1,4 @@ -const RT=(()=>{try{const el=document.getElementById('rt-config');return el?JSON.parse(el.textContent):{}}catch(e){return {}}})(); +const RT=(()=>{try{const el=document.getElementById('rt-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})(); /* eslint-disable */ /* ═══════════ v5.13.0 Realtime — WS gateway, présence, curseurs, merge LWW ═══════════ */ diff --git a/static/js/settings.js b/static/js/settings.js index 41d8e7a..ccc7f91 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -1,4 +1,5 @@ -const ST=(()=>{try{const el=document.getElementById('st-config');return el?JSON.parse(el.textContent):{}}catch(e){return {}}})(); +/* exported settingsInit -- appeles depuis les attributs HTML des templates */ +const ST=(()=>{try{const el=document.getElementById('st-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})(); function settingsInit() { return { @@ -107,13 +108,13 @@ function settingsInit() { var r = await fetch('/api/notifications/prefs', {credentials:'same-origin'}); var d = await r.json(); this.notifPrefs = Object.assign({comments:true, mentions:true, reminders:true, assignments:true}, d.prefs || {}); - } catch(e) {} + } catch { /* volontaire */ } try { var tz = await fetch('/api/notifications/timezone', {credentials:'same-origin'}); var td = await tz.json(); this.userTimezone = td.timezone || ''; this.userZones = td.zones || []; - } catch(e) {} + } catch { /* volontaire */ } }, async saveNotifPrefs() { try { @@ -121,7 +122,7 @@ function settingsInit() { method: 'POST', headers: {'Content-Type':'application/json','X-CSRF-Token': this.getCsrfToken()}, body: JSON.stringify(this.notifPrefs) }); - } catch(e) {} + } catch { /* volontaire */ } }, async saveTimezone() { try { @@ -129,8 +130,8 @@ function settingsInit() { method: 'POST', headers: {'Content-Type':'application/json','X-CSRF-Token': this.getCsrfToken()}, body: JSON.stringify({timezone: this.userTimezone}) }); - if (r.ok && typeof toast === 'function') toast('Timezone saved'); - } catch(e) {} + if (r.ok && typeof window.showToast === 'function') window.showToast('Timezone saved'); + } catch { /* volontaire */ } }, async loadTags() { @@ -138,7 +139,7 @@ function settingsInit() { var r = await fetch('/api/settings/tags/all'); var d = await r.json(); this.allTags = d.tags || []; - } catch(e) { this.allTags = []; } + } catch { this.allTags = []; } }, async createTag() { @@ -169,7 +170,6 @@ function settingsInit() { this.renamingTag = tag; this.renameValue = tag.name; // Focus the input after Alpine renders the modal - var self = this; this.$nextTick(function() { var input = document.querySelector('.modal-box input[type=text]'); if (input) { input.focus(); input.select(); } @@ -213,7 +213,7 @@ function settingsInit() { var r = await this.adminFetch('/api/admin/users'); var d = await r.json(); this.adminUsers = d.users || []; - } catch(e) { this.adminUsers = []; } + } catch { this.adminUsers = []; } await this.loadAdminStats(); }, @@ -221,7 +221,7 @@ function settingsInit() { try { var r = await this.adminFetch('/api/admin/stats'); this.adminStats = await r.json(); - } catch(e) { this.adminStats = {}; } + } catch { this.adminStats = {}; } }, async loadAdminAudit() { @@ -249,7 +249,7 @@ function settingsInit() { const logs = d.logs || []; this.auditLogs = (reset !== false) ? logs : this.auditLogs.concat(logs); this.auditHasMore = logs.length >= this.auditPageSize; - } catch(e) { + } catch { if (reset !== false) this.auditLogs = []; this.auditHasMore = false; } @@ -278,7 +278,7 @@ function settingsInit() { var d = await r.json(); window.showToast(d.error || 'Failed to create user', 'error'); } - } catch(e) { window.showToast('Network error', 'error'); } + } catch { window.showToast('Network error', 'error'); } }, async adminUpdateUser() { @@ -295,7 +295,7 @@ function settingsInit() { this.editingUser = null; await this.loadAdminUsers(); } - } catch(e) {} + } catch { /* volontaire */ } }, async adminDeleteUser(id) { @@ -303,7 +303,7 @@ function settingsInit() { try { await this.adminFetch('/api/admin/users/' + id, { method: 'DELETE' }); await this.loadAdminUsers(); - } catch(e) {} + } catch { /* volontaire */ } }, // ── LLM / Agent ── @@ -447,7 +447,7 @@ function settingsInit() { } if (this.selectedProviderId) this.keyForm(this.selectedProviderId); this.syncLlmStatus(); - } catch(e) {} + } catch { /* volontaire */ } }, async loadProviderModels(id) { var f = this.keyForm(id); @@ -474,7 +474,7 @@ function settingsInit() { } else { f.msg = 'Erreur: ' + (d.error || 'échec de connexion'); } - } catch(e) { f.msg = 'Erreur réseau'; } + } catch { f.msg = 'Erreur réseau'; } finally { f.modelLoading = false; } }, async saveUserKey(id) { @@ -505,7 +505,7 @@ function settingsInit() { } else { f.msg = d.detail || 'Erreur lors de l\'enregistrement'; } - } catch(e) { f.msg = 'Erreur réseau'; } + } catch { f.msg = 'Erreur réseau'; } finally { f.saving = false; } }, globalNeedsKeyFor(id) { @@ -534,7 +534,7 @@ function settingsInit() { this._setProviderState(id, false, d.error || 'échec de connexion'); f.msg = '✗ Test échoué : ' + (d.error || 'échec de connexion'); } - } catch(e) { + } catch { this._setProviderState(id, false, 'Erreur réseau'); f.msg = 'Erreur réseau'; } @@ -559,7 +559,7 @@ function settingsInit() { } else { f.msg = d.detail || 'Erreur lors de la suppression'; } - } catch(e) { f.msg = 'Erreur réseau'; } + } catch { f.msg = 'Erreur réseau'; } finally { f.deleting = false; } }, async loadLlmConfig() { @@ -576,7 +576,7 @@ function settingsInit() { api_base: d.api_base || (defp && defp.base_url) || ''}; this.applyLlmModels(); this.syncLlmStatus(); - } catch(e) {} + } catch { /* volontaire */ } }, applyLlmModels() { var p = this.providerById(this.llmForm.provider); @@ -619,7 +619,7 @@ function settingsInit() { } else { this.llmMsg = d.detail || 'Erreur lors de l\'enregistrement'; } - } catch(e) { this.llmMsg = 'Erreur réseau'; } + } catch { this.llmMsg = 'Erreur réseau'; } finally { this.llmSaving = false; } }, async resetGlobalLlm() { @@ -644,7 +644,7 @@ function settingsInit() { } else { this.llmMsg = d.detail || 'Erreur lors de la réinitialisation'; } - } catch(e) { this.llmMsg = 'Erreur réseau'; } + } catch { this.llmMsg = 'Erreur réseau'; } finally { this.llmSaving = false; } }, async testLlm() { @@ -669,7 +669,7 @@ function settingsInit() { this._setProviderState(this.llmForm.provider, d.ok, d.error || ''); if (d.ok) cur.verified_model = d.model || ''; } - } catch(e) { this.llmTestOk = false; this.llmTestMsg = 'Erreur réseau'; } + } catch { this.llmTestOk = false; this.llmTestMsg = 'Erreur réseau'; } finally { this.llmTesting = false; } }, getCompletedLlmTest() { return this.llmTestMsg; }, @@ -698,7 +698,7 @@ function settingsInit() { } else { this.accountMsg = '✗ ' + (d.error || 'Error'); } - } catch(e) { this.accountMsg = '✗ Network error'; } + } catch { this.accountMsg = '✗ Network error'; } this.accountSaving = false; }, @@ -737,7 +737,7 @@ function settingsInit() { var r = await fetch('/api/gitea/status'); var d = await r.json(); this.giteaLinked = d.linked || false; - } catch(e) { this.giteaLinked = false; } + } catch { this.giteaLinked = false; } }, async disconnectGitea() { @@ -745,41 +745,41 @@ function settingsInit() { try { var r = await fetch('/api/gitea/disconnect', { method: 'DELETE', headers: {'X-CSRF-Token': this.getCsrfToken()} }); if (r.ok) { this.giteaLinked = false; } - } catch(e) {} + } catch { /* volontaire */ } }, async loadGithubStatus() { try { var r = await fetch('/api/github/status'); var d = await r.json(); this.githubLinked = d.linked || false; - } catch(e) { this.githubLinked = false; } + } catch { this.githubLinked = false; } }, async disconnectGithub() { if (!confirm('Disconnect GitHub? You can reconnect anytime.')) return; try { var r = await fetch('/api/github/disconnect', { method: 'DELETE', headers: {'X-CSRF-Token': this.getCsrfToken()} }); if (r.ok) { this.githubLinked = false; } - } catch(e) {} + } catch { /* volontaire */ } }, async loadAutomations() { - try { await this.loadCollectionsForForm(); } catch(e) {} + try { await this.loadCollectionsForForm(); } catch { /* volontaire */ } try { var r = await fetch('/workspace/automations', {credentials:'same-origin'}); var d = await r.json(); this.automations = d.automations || []; - } catch(e) { this.automations = []; } + } catch { this.automations = []; } }, async loadCollectionsForForm() { try { var r = await fetch('/db/api', {credentials:'same-origin'}); var d = await r.json(); this.globalCollections = d.collections || []; - } catch(e) { this.globalCollections = []; } + } catch { this.globalCollections = []; } }, async saveAutomation() { if (!this.af.name.trim()) { window.showToast && window.showToast('Nom requis', 'error'); return; } - try { JSON.parse(this.af.condition_json || '[]'); } catch(e) { window.showToast && window.showToast('Conditions JSON invalide', 'error'); return; } - try { JSON.parse(this.af.actions_json || '[]'); } catch(e) { window.showToast && window.showToast('Actions JSON invalide', 'error'); return; } + try { JSON.parse(this.af.condition_json || '[]'); } catch { window.showToast && window.showToast('Conditions JSON invalide', 'error'); return; } + try { JSON.parse(this.af.actions_json || '[]'); } catch { window.showToast && window.showToast('Actions JSON invalide', 'error'); return; } var payload = { name: this.af.name, trigger_type: this.af.trigger_type, @@ -801,7 +801,7 @@ function settingsInit() { window.showToast && window.showToast(this.editAutomationId ? 'Automation mise à jour' : 'Automation créée'); this.cancelEditAutomation(); await this.loadAutomations(); - } catch(e) { window.showToast && window.showToast('Erreur réseau', 'error'); } + } catch { window.showToast && window.showToast('Erreur réseau', 'error'); } }, editAutomation(a) { this.editAutomationId = a.id; @@ -837,7 +837,7 @@ function settingsInit() { headers: {'Content-Type':'application/json','X-CSRF-Token': this.getCsrfToken()}, body: JSON.stringify(payload) }); if (r.ok) await this.loadAutomations(); - } catch(e) {} + } catch { /* volontaire */ } }, async runAutomation(id) { try { @@ -850,7 +850,7 @@ function settingsInit() { this.autoRuns.push({ created_at: new Date().toLocaleString(), trigger_source: 'manual', status: d.status || 'fired', detail: d.detail || '' }); window.showToast && window.showToast('Résultat : ' + (d.status || '') + ' — ' + (d.detail || '')); await this.loadAutomations(); - } catch(e) {} + } catch { /* volontaire */ } }, async deleteAutomation(id) { if (!confirm('Supprimer cette automation ?')) return; @@ -858,7 +858,7 @@ function settingsInit() { var r = await fetch('/workspace/automations/' + id, { method:'DELETE', credentials:'same-origin', headers: {'X-CSRF-Token': this.getCsrfToken()} }); if (r.ok) await this.loadAutomations(); - } catch(e) {} + } catch { /* volontaire */ } }, // ── v5.2.0 API tokens ── async loadApiTokens() { @@ -866,7 +866,7 @@ function settingsInit() { var r = await fetch('/api/settings/tokens', {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, credentials:'same-origin'}); var d = await r.json(); this.apiTokens = d.tokens || []; - } catch(e) { this.apiTokens = []; } + } catch { this.apiTokens = []; } }, async createApiToken() { var name = this.tokenName.trim(); @@ -879,12 +879,12 @@ function settingsInit() { this.newToken = d; this.tokenName = ''; await this.loadApiTokens(); - } catch(e) { window.showToast && window.showToast('Erreur réseau', 'error'); } + } catch { window.showToast && window.showToast('Erreur réseau', 'error'); } }, async copyNewToken() { if (!this.newToken) return; try { await navigator.clipboard.writeText(this.newToken.token); window.showToast && window.showToast('Token copié'); } - catch(e) { window.showToast && window.showToast(this.newToken.token, 'error'); } + catch { window.showToast && window.showToast(this.newToken.token, 'error'); } }, async revokeApiToken(t) { if (!confirm('Révoquer le token « ' + t.name + ' » ? Il ne fonctionnera plus immédiatement.')) return; @@ -892,7 +892,7 @@ function settingsInit() { var r = await fetch('/api/settings/tokens/' + t.id, {method:'DELETE', credentials:'same-origin', headers: {'X-CSRF-Token': this.getCsrfToken()} }); if (r.ok) await this.loadApiTokens(); - } catch(e) {} + } catch { /* volontaire */ } }, // ── v5.2.0 Sessions ── async loadSessions() { @@ -900,7 +900,7 @@ function settingsInit() { var r = await fetch('/api/settings/sessions', {credentials:'same-origin'}); var d = await r.json(); this.sessions = d.sessions || []; - } catch(e) { this.sessions = []; } + } catch { this.sessions = []; } }, async revokeSession(s) { var msg = s.is_current ? 'Déconnecter cet appareil ?' : 'Révoquer cette session ?'; @@ -912,7 +912,7 @@ function settingsInit() { if (s.is_current) window.location = '/auth/login?provider=local'; await this.loadSessions(); } - } catch(e) {} + } catch { /* volontaire */ } }, // ── v6.2.0 Web Clipper ── async loadClipperDevices() { @@ -920,14 +920,14 @@ function settingsInit() { var r = await fetch('/api/v2/web-clipper/devices', {credentials:'same-origin'}); var d = await r.json(); this.clipperDevices = d.devices || []; - } catch(e) { this.clipperDevices = []; } + } catch { this.clipperDevices = []; } }, async revokeClipperDevice(d) { if (!confirm('Révoquer l\'extension « ' + (d.device_name || d.device_id) + ' » ?')) return; try { var r = await fetch('/api/v2/web-clipper/devices/' + d.id, {method:'DELETE', credentials:'same-origin', headers: {'X-CSRF-Token': this.getCsrfToken()}}); if (r.ok) await this.loadClipperDevices(); - } catch(e) {} + } catch { /* volontaire */ } }, async testClip() { this.clipTesting = true; this.clipTestMsg = ''; this.clipTestOk = false; @@ -940,7 +940,7 @@ function settingsInit() { var d = await r.json(); if (r.ok) { this.clipTestOk = true; this.clipTestMsg = '✓ Page créée : #' + d.page_id + ' — ' + d.title; await this.loadClipperDevices(); } else this.clipTestMsg = d.detail || 'Erreur'; - } catch(e) { this.clipTestMsg = 'Erreur réseau'; } + } catch { this.clipTestMsg = 'Erreur réseau'; } finally { this.clipTesting = false; } }, // ── v6.7.0 SSO / Enterprise (admin) ── @@ -957,8 +957,8 @@ function settingsInit() { async copySsoMetadata() { try { await navigator.clipboard.writeText(this.ssoMetadataUrl()); - if (typeof toast === 'function') toast('SP metadata URL copiée'); - } catch(e) {} + if (typeof window.showToast === 'function') window.showToast('SP metadata URL copiée'); + } catch { /* volontaire */ } }, async loadSsoConfig() { this.ssoMsg = ''; @@ -994,7 +994,7 @@ function settingsInit() { this.ssoGroups = (d.groups_mapping || []).map(function(g){ return {sso_group: g.sso_group || '', workspace_role: g.workspace_role || 'editor', workspace_id: g.workspace_id || ''}; }); } } - } catch(e) {} + } catch { /* volontaire */ } try { var w = await fetch('/api/v2/sso/workspaces', {credentials:'same-origin'}); if (w.ok) { @@ -1002,13 +1002,13 @@ function settingsInit() { this.ssoWorkspaces = wd.workspaces || []; this.ssoProvisioned = wd.provisioned_users || this.ssoProvisioned; } - } catch(e) {} + } catch { /* volontaire */ } }, async saveSsoConfig() { this.ssoMsg = ''; var amap = {}; try { amap = JSON.parse(this.ssoAttrJson || '{}'); } - catch(e) { this.ssoMsg = 'Attribute mapping : JSON invalide'; this.ssoOk = false; return; } + catch { this.ssoMsg = 'Attribute mapping : JSON invalide'; this.ssoOk = false; return; } if (amap === null || typeof amap !== 'object' || Array.isArray(amap)) { this.ssoMsg = 'Attribute mapping : doit être un objet JSON'; this.ssoOk = false; return; } @@ -1024,10 +1024,10 @@ function settingsInit() { delete payload.id; try { var r = await this.adminFetch('/api/v2/sso/config', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)}); - var d = null; try { d = await r.json(); } catch(e) {} + var d = null; try { d = await r.json(); } catch { /* volontaire */ } if (!r.ok) { this.ssoMsg = (d && (d.detail || d.error)) || ('Erreur ' + r.status); this.ssoOk = false; } else { this.ssoMsg = 'Configuration enregistrée'; this.ssoOk = true; await this.loadSsoConfig(); } - } catch(e) { this.ssoMsg = 'Erreur réseau'; this.ssoOk = false; } + } catch { this.ssoMsg = 'Erreur réseau'; this.ssoOk = false; } finally { this.ssoSaving = false; } }, async disableSso() { @@ -1037,16 +1037,16 @@ function settingsInit() { var r = await this.adminFetch('/api/v2/sso/config', {method:'DELETE'}); if (r.ok) { this.ssoMsg = 'SSO désactivé'; this.ssoOk = true; await this.loadSsoConfig(); } else { this.ssoMsg = 'Erreur ' + r.status; this.ssoOk = false; } - } catch(e) { this.ssoMsg = 'Erreur réseau'; this.ssoOk = false; } + } catch { this.ssoMsg = 'Erreur réseau'; this.ssoOk = false; } }, async syncSsoGroups() { this.ssoMsg = ''; try { var r = await this.adminFetch('/api/v2/sso/sync', {method:'POST', headers:{'Content-Type':'application/json'}, body:'{}'}); - var d = null; try { d = await r.json(); } catch(e) {} + var d = null; try { d = await r.json(); } catch { /* volontaire */ } if (r.ok) { this.ssoMsg = 'Groupes re-synchronisés — ' + (d && d.updated || 0) + '/' + (d && d.users || 0) + ' utilisateur(s)'; this.ssoOk = true; await this.loadSsoConfig(); } else { this.ssoMsg = (d && d.detail) || ('Erreur ' + r.status); this.ssoOk = false; } - } catch(e) { this.ssoMsg = 'Erreur réseau'; this.ssoOk = false; } + } catch { this.ssoMsg = 'Erreur réseau'; this.ssoOk = false; } }, addSsoGroup() { this.ssoGroups.push({sso_group:'', workspace_role:'editor', workspace_id: this.ssoCfg.default_workspace_id || ''}); }, removeSsoGroup(i) { this.ssoGroups.splice(i, 1); }, @@ -1056,7 +1056,7 @@ function settingsInit() { var r = await this.adminFetch('/api/settings/backups'); var d = await r.json(); this.backups = d.backups || []; - } catch(e) { this.backups = []; } + } catch { this.backups = []; } }, async runBackup() { this.backupRunning = true; this.backupMsg = ''; this.backupOk = false; @@ -1065,7 +1065,7 @@ function settingsInit() { var d = await r.json(); if (r.ok) { this.backupOk = true; this.backupMsg = '✓ Sauvegarde créée : ' + d.filename; await this.loadBackups(); } else { this.backupMsg = d.detail || 'Erreur'; } - } catch(e) { this.backupMsg = 'Erreur réseau'; } + } catch { this.backupMsg = 'Erreur réseau'; } finally { this.backupRunning = false; } }, closeSettings() { diff --git a/static/js/workspaces.js b/static/js/workspaces.js index 2a7542c..54d99f3 100644 --- a/static/js/workspaces.js +++ b/static/js/workspaces.js @@ -1,3 +1,4 @@ +/* exported workspacesPage -- appeles depuis les attributs HTML des templates */ function workspacesPage() { return { @@ -105,7 +106,7 @@ function workspacesPage() { myLogin = meData.user?.login || 'Me'; if (myLogin.includes('_')) myLogin = myLogin.split('_').pop(); // strip gitea_ prefix if present } - } catch(e) {} + } catch { /* volontaire */ } // User repos (no org filter) try { @@ -117,7 +118,7 @@ function workspacesPage() { groups.push({org: myLogin, avatar: '', projects: repos.map(r => ({...r, owner: r.owner || {login: myLogin}}))}); } } - } catch(e) {} + } catch { /* volontaire */ } // Org repos for (const org of orgs) { @@ -129,13 +130,13 @@ function workspacesPage() { groups.push({org: org.username || org.login || org.name, avatar: org.avatar_url || '', projects: d.projects}); } } - } catch(e) {} + } catch { /* volontaire */ } } this.giteaGroups = groups; this.giteaTotal = groups.reduce((sum, g) => sum + g.projects.length, 0); this.giteaStatus = 'ok'; - } catch(e) { + } catch { this.giteaStatus = 'error'; } },