- une zone de dépôt par cible à la place du menu déroulant, avec pastille
de catégorie sur chaque fichier quand il y a plusieurs cibles (#11)
- un seul sélecteur : le doublon « Parcourir un dossier » disparaît, les
dossiers passent par glisser-déposer (#10)
- bouton « Tout mettre en pause / Tout reprendre » à côté de « Tout annuler » (#13)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- bouton Pause/Reprendre par fichier (#6)
- bouton Réessayer sur les échecs (#7)
- noms de fichiers tronqués avec ellipse + titre au survol (#5)
- contenu pleine largeur (#4)
- cible figée à l'ajout, nouvelle tentative d'init sur 429 (#1, #3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`GET /` décide côté serveur, à chaque requête, laquelle des deux pages
servir : `public/login.html` (aucune session valide — juste le bouton
« Se connecter », aucune zone de dépôt dans le HTML) ou
`public/upload.html` (session valide — nom d'utilisateur injecté et
échappé, bouton « Se déconnecter »). Nouveau `src/routes/pages.ts`.
Simplifications côté page de dépôt, permises par cette garantie
serveur : plus de fetch `/auth/me` ni de bascule `authenticated` en
JS, plus de retrait/réinsertion de la zone de dépôt dans le DOM — elle
est toujours présente puisque la page n'est servie qu'aux sessions
valides. Une session qui expire en cours d'usage (401 sur /targets ou
/upload*) renvoie vers `/?login=expired`, qui affiche alors la vraie
page de connexion avec le message adapté.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`targets[].allowedMimeTypes` : chaque cible peut définir sa propre
politique (mêmes formes : "all", types exacts, jokers "image/*"…).
Absente => la cible hérite du `allowedMimeTypes` global, qui devient
la valeur par défaut. Résolu au parsing : chaque `DropTarget` porte
toujours une politique.
- `POST /upload` et `POST /upload/init` valident contre
`target.allowedMimeTypes` (au lieu de la valeur globale).
- `GET /targets` renvoie `allowedMimeTypes` ; la page en déduit
l'attribut `accept` du sélecteur de fichiers (le serveur reste
l'autorité — le glisser-déposer n'est pas filtré).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- `#list` plafonné à 55vh avec défilement interne (au lieu d'allonger la page).
- En-tête d'auth : suppression du <span> qui cassait l'alignement ; « Connecté : … »
et le bouton « Se déconnecter » sont alignés (flex, space-between).
- Les zones de dépôt (`#dropzone` + `#dirRow`, regroupées dans `#dropArea`) sont
retirées du DOM tant que l'on n'est pas connecté, réinsérées à la connexion.
- Ajout de `[hidden] { display: none !important }` : des règles `display` d'auteur
(`.btn`, `#queueRow`) neutralisaient l'attribut `hidden` — bouton de déconnexion
et barre « Tout annuler » restaient visibles hors contexte.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Remplace les 3 workers par fichier par un pool global de morceaux en vol,
réparti équitablement entre les fichiers fragmentés actifs : 1 fichier
utilise tout le pool, N fichiers en reçoivent une part chacun
(ceil(taille / N)), la capacité libérée par un fichier terminé est
redistribuée. Un fichier en cours d'init compte dans la part pour ne pas
être affamé ; les morceaux déjà en vol ne sont jamais interrompus.
La taille du pool est réglable via `chunkPoolSize` (config.json, défaut
30, borné à 256) et transmise au navigateur par un nouveau `GET /config`.
Corps des morceaux repassé en streaming (writeChunkAt sur un flux + garde
`maxBytes`) : mémoire ~constante même à pool élevé, plus de bufferisation
de 16 Mo par requête.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Serveur :
- POST /upload/init -> session { id, chunkSize (16 Mo), chunkCount }
- PUT /upload/:id/:index -> écrit le morceau à son offset (octet-stream brut,
bufferisé, parser dédié) ; renvoie { received, total }
- POST /upload/:id/complete -> vérifie tous les morceaux + la taille, renomme
le .mediadrop-part vers le chemin final ; 201 + métadonnées
- DELETE /upload/:id -> abandon, supprime le fichier partiel
- src/lib/upload-sessions.ts : registre en mémoire, purge après 30 min
d'inactivité, 100 sessions max
- src/lib/targets.ts : resolveTarget / resolvePlacement extraits et partagés
- writeChunkAt / createEmptyFile / finalizeFile / removeFile dans storage.ts
- limites, allowDirectories, jokers MIME et confinement dans la cible
s'appliquent aussi au chemin fragmenté
Page :
- bascule automatique en fragmenté au-delà de 32 Mo : 3 morceaux en parallèle,
3 réessais par morceau, progression et annulation agrégées
- makeReporter partagé (débit + ETA) ; job.cancel unifié (simple / fragmenté /
en file d'attente)
Tests : +tests/upload-chunked.test.ts (9), +writeChunkAt (storage) ; 73 tests.
Vérifié en live : 40 Mio -> 3 morceaux // -> octets exacts, DELETE nettoie.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Progression en temps réel par fichier : transféré / total, %, débit
(moyenne glissante), ETA.
- Annulation : bouton « Annuler » par fichier (en attente ou en cours) +
« Tout annuler ». xhr.abort() -> le serveur nettoie déjà le fichier partiel.
- File d'attente : au plus 3 transferts simultanés, le reste patiente
(évite de saturer le lien sur un dépôt de dossier). Bandeau récap.
Changements purement page ; aucune modif serveur. 62 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dépôt de dossiers :
- flag `allowDirectories` (défaut false) par cible dans config.json + schéma.
- POST /upload accepte `?path=<chemin relatif>` : l'arborescence est recréée
sous `dir`, nom d'origine conservé, fichier écrasé si présent.
- src/lib/paths.ts : assainissement segment par segment (pas de `..`, pas de
séparateur) + vérification de confinement dans storage.ts -> aucune sortie
possible de la cible. Refus 400 si la cible n'autorise pas les dossiers.
- GET /targets expose `allowDirectories`.
- Page : glisser-déposer d'un dossier (parcours récursif webkitGetAsEntry) +
bouton « parcourir un dossier », visibles selon la cible.
Très gros fichiers (~10 Go) :
- `requestTimeout: 0` / `connectionTimeout: 0` : aucun timeout serveur imposé.
- écriture disque avec un tampon de 1 Mio.
- doc : `maxFileSizeMb` à l'échelle Go, réglages reverse proxy.
Tests : +paths.test.ts, +placement (storage), +dossier & gros fichier (upload) ;
60 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Serveur TypeScript (Fastify 5) permettant de déposer des fichiers médias
dans des dossiers nommés, via une page web de glisser-déposer.
- Authentification OpenID Connect gérée côté serveur (Authorization Code +
PKCE, client confidentiel, openid-client) ; session via cookie chiffré
(@fastify/secure-session).
- Configuration dans un fichier JSON (schéma fourni) ; l'environnement ne
porte que le pointeur MEDIADROP_CONFIG.
- Cibles de dépôt nommées : liste { name, label, dir }, sélection par
?target=, première cible par défaut.
- Upload multipart en streaming : nom de fichier dérivé côté serveur (pas
de path traversal), liste blanche de types MIME, plafond de taille.
- TypeScript strict, ESLint + Prettier sans warning, 46 tests Vitest.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>