Deux widgets custom : Upload de fichiers & Visionneuse multimédia

Bonjour à tous,

Je partage deux widgets personnalisés que j’ai développés et qui fonctionnent en tandem ou seuls. Ils sont hébergés sur GitHub Pages, et ne nécessitent aucune installation de serveur.

Vous pouvez les tester directement sur cette instance publique : https://docs.getgrist.com/94rvRb94Zq1n/Upload-de-fichier?utm_id=share-doc


1. Widget Upload de fichiers

URL du widget : https://isaytoo.github.io/grist-upload-widget

Ce widget permet d’uploader n’importe quel fichier (image, vidéo, PDF, audio…) vers un service de stockage cloud et d’enregistrer automatiquement l’URL résultante dans une colonne de votre document Grist.

Pourquoi c’est utile ? Sans ce widget, stocker un fichier et récupérer son URL publique dans Grist demande de passer par des outils externes. Ici, tout se fait sans quitter Grist : vous choisissez votre fichier, il part sur votre cloud, et l’URL atterrit directement dans la cellule de la ligne sélectionnée.

Providers supportés :

  • :cloud: Cloudinary
  • :high_voltage: Supabase Storage
  • :bucket: AWS S3 / compatible (Cloudflare R2, Backblaze B2, MinIO…)
  • :card_index_dividers: Nextcloud (nouveau)
  • :wrench: Endpoint POST personnalisé

Fonctionnalités principales :

  • Upload universel — images, vidéos, PDF, audio, et tout autre type de fichier
  • Sauvegarde automatique de l’URL dans la colonne Grist mappée
  • Barre de progression en temps réel
  • Thème clair/sombre automatique (suit l’OS ou Grist)
  • Configuration persistante (sauvegardée dans les options du widget)
  • Fichier unique, aucune dépendance externe

Concernant Nextcloud : Le widget upload vers Nextcloud via WebDAV et crée automatiquement un lien de partage public. Il suffit de renseigner l’URL de votre instance, votre nom d’utilisateur, et un App Token (à générer dans Nextcloud → Paramètres → Sécurité → Mots de passe d’application). Le dossier de destination est créé automatiquement s’il n’existe pas (défaut : GristUploads).

Concernant la sécurité des identifiants : Les identifiants sont stockés dans les options du widget Grist, directement dans votre document — pas sur un serveur externe, pas dans le localStorage du navigateur. Ils sont visibles uniquement par les administrateurs du document. Pour Nextcloud, il est recommandé d’utiliser un App Token plutôt que votre mot de passe principal, et pour S3, des clés aux permissions minimales (écriture seule, bucket restreint).

Comment l’installer :

  1. Dans votre document Grist, ajoutez un widget « Personnalisé »
  2. Collez l’URL : https://isaytoo.github.io/grist-upload-widget
  3. Niveau d’accès : Accès complet au document
  4. Dans le Panneau Créateur, mappez la « Colonne de destination » (une colonne Text qui recevra l’URL)
  5. Au premier chargement, renseignez votre provider dans l’écran de configuration

Note pour les instances self-hosted : si votre Grist bloque les appels CORS vers votre provider cloud, l’extension Chrome Grist Widget Bridge résout le problème.

Code source : https://github.com/isaytoo/grist-upload-widget


2. Widget Visionneuse multimédia

URL du widget : https://isaytoo.github.io/grist-media-viewer-widget

Ce widget affiche directement dans Grist le fichier dont l’URL est stockée dans la ligne sélectionnée. Il est conçu pour fonctionner en complément du widget Upload ci-dessus, mais fonctionne avec n’importe quelle colonne contenant une URL de fichier.

Fonctionnalités principales :

  • Images — zoom à la molette, déplacement, rotation, mode plein écran
  • PDF — affichage intégré via le lecteur natif du navigateur
  • Vidéo — lecteur HTML5 natif (MP4, WebM, MOV…)
  • Audio — lecteur HTML5 natif (MP3, WAV, FLAC…)
  • Texte/Code — affichage monospace coloré (TXT, JSON, CSV, Markdown…)
  • Galerie avec miniatures si une cellule contient plusieurs URLs (séparées par espace ou virgule)
  • Raccourcis clavier : / navigation, +/- zoom, F plein écran, Échap fermer
  • Swipe mobile (écrans tactiles)
  • Bouton de téléchargement
  • Détection automatique du type depuis l’extension de l’URL

Comment l’installer :

  1. Dans votre document Grist, ajoutez un widget « Personnalisé »
  2. Collez l’URL : https://isaytoo.github.io/grist-media-viewer-widget
  3. Dans le Panneau Créateur, mappez les colonnes :
  • URL du fichier (obligatoire) — colonne contenant l’URL (ou plusieurs URLs) des fichiers
  • Nom du fichier (optionnel) — colonne pour un nom d’affichage
  1. Sélectionnez une ligne → le fichier s’affiche automatiquement

Code source : https://github.com/isaytoo/grist-media-viewer-widget


Workflow typique avec les deux widgets ensemble

Avant tout, créez une table dédiée dans votre document Grist (ex. LES_LIENS_URL) avec au minimum une colonne de type Texte (ex. lien) — c’est dans cette colonne que les URLs uploadées seront enregistrées dynamiquement à chaque upload.

Placez ensuite les deux widgets côte à côte sur la même page Grist, liés à cette table :

  • Widget Upload (gauche) → vous uploadez un fichier, l’URL s’écrit automatiquement dans la colonne lien de la ligne sélectionnée
  • Widget Visionneuse (droite) → le fichier correspondant s’affiche immédiatement

Chaque nouvelle ligne de la table correspond à un fichier uploadé, ce qui vous constitue au fil du temps une médiathèque directement dans Grist.

Vous pouvez tester les deux widgets directement sur cette instance publique Grist :
https://docs.getgrist.com/94rvRb94Zq1n/Upload-de-fichier?utm_id=share-doc

image

image

image

image

image

image

1 « J'aime »

hello @Isaytoo , super utile ça rejoint le besoin évoqué pour upload dans nextcloud et créer un lien, par contre où sont stockés les identifiants/clé API du cloud ?

hello @audezu,

Les identifiants sont stockés dans les options du widget Grist (via grist.setOption()), directement dans votre document Grist — pas sur un serveur externe, pas dans le localStorage du navigateur. Ils persistent entre les sessions et ne sortent jamais de votre instance Grist.

Un point important à noter cependant : ces options sont visibles par les administrateurs du document.
Le widget l’indique d’ailleurs explicitement pour les clés S3 : mieux vaut utiliser des clés aux permissions minimales (écriture seule, bucket restreint).

Concernant Nextcloud : bonne nouvelle, je viens justement d’ajouter le support Nextcloud suite à votre retour !
Le widget uploade via WebDAV et crée automatiquement un lien de partage public via l’OCS API de Nextcloud.
Il suffit de renseigner l’URL de votre instance, votre nom d’utilisateur, et un App Token (à générer dans Nextcloud → Paramètres → Sécurité → Mots de passe d’application).
Le dossier de destination est créé automatiquement s’il n’existe pas.
La mise à jour est déjà disponible à la même URL : https://isaytoo.github.io/grist-upload-widget

eish il ne faut pas stocker de clé api ou mot de passe ni dans les options du widget ni dans une table car la clé/mdp va être stockée en clair, donc gros problemo en cas de partage/export du doc, mais aussi imaginons une attaque de la bdd de l’instance ce serait une hécatombe en cascade… je laisse @hexaltation et @florent.fayolle détailler les risques et également les solutions, ils seront plus précis que moi !

super chouette pour Nextcloud, j’avais aussi commencé un prototype avec lien public, l’étape d’après c’est l’upload dans nextcloud avec mise en place de permissions correspondant à celles du grist - mais connaissant le retour du get permissions sur un doc grist, je n’ai même pas voulu essayer :slight_smile: (par ailleurs a-t-on accès aux acl depuis un custom widget ?)

Vous avez tout à fait raison sur le risque sécurité, et c’est un point important.
Le widget affiche déjà un avertissement dans l’interface pour S3 et Nextcloud (recommandation d’utiliser un App Token plutôt qu’un mot de passe principal, clés S3 en permissions minimales), mais ça reste insuffisant face au risque que vous décrivez : export accidentel, partage de doc, compromission de la base.

La solution propre serait un proxy serveur qui détient les credentials et expose un simple endpoint d’upload : le widget n’aurait alors aucune clé à stocker.
Mais ça va à l’encontre de l’objectif initial du widget (« aucune installation de serveur »). C’est un compromis assumé, mais qui mérite d’être dit clairement aux utilisateurs.

En attendant, les mitigations pratiques :

  • Nextcloud : App Token révocable à tout moment, avec permissions réduites à un seul dossier
  • Cloudinary : l’Upload Preset unsigned est conçu pour être public par nature, il ne donne accès qu’à l’upload, pas au compte
  • S3 : clé IAM en écriture seule sur un bucket dédié, sans accès à d’autres ressources

Sur la question des ACL Grist depuis un widget custom : à ma connaissance, le Grist Plugin API n’expose pas les règles d’accès granulaires du document.
On a accès à l’utilisateur courant via certains callbacks mais pas aux ACL elles-mêmes.
Ce serait une feature très intéressante à soumettre à l’équipe Grist : un grist.getPermissions() qui retournerait les droits de l’utilisateur courant ouvrirait des possibilités très sympas pour les widgets avancés.

Le scope limité (s’il est possible) limiterait les dégâts mais ce n’est pas du tout suffisant, il faut effectivement utiliser un proxy (ou un workflow n8n par exemple, il me semble que @Arthur_Panckoucke avait fait comme ça pour l’un de ses cw) qui permet entre autre de faire de la validation, de la limitation de nombre de fichiers uploadés…

Bref, on ne stocke pas une clé ou un mdp en clair côté client, et en plus des raisons citées dans le post précédent, tous les éditeurs pourraient y accéder dans l’en-tête des requêtes (cf ci-dessous), idem pour les extensions de navigateur qui ont accès aux requêtes, ou tout proxy sur le chemin…


C’est sûr que c’est moins facile, mais c’est pour la bonne cause :slight_smile:

Merci pour cette démonstration très claire. Vous avez entièrement raison, et c’est exactement ce qui m’a motivé à construire une vraie solution.

J’ai ajouté un mode « Proxy sécurisé » au widget Upload. Voici ce que ça change et comment le mettre en place.


Pourquoi un proxy ?

Sans proxy, le widget fait ses appels cloud directement depuis le navigateur. Les credentials (token Nextcloud, clé S3, preset Cloudinary…) sont donc visibles dans les devtools, les extensions Chrome, et tout proxy réseau. Comme vous l’avez parfaitement illustré.

Avec le proxy, le navigateur ne voit plus jamais les vrais credentials. Il envoie juste le fichier à une URL intermédiaire, et c’est cette URL qui fait l’appel authentifié vers le cloud. Les credentials restent chiffrés côté serveur.


Ce que voit un attaquant maintenant dans les devtools

POST https://grist-upload-proxy.votresubdomain.workers.dev
X-Grist-Token: eyJhbGciOiJIUzI1NiJ9...

L’URL du proxy et un token temporaire Grist. c’est tout. Aucune clé cloud, aucun mot de passe. Et ce token est inutilisable sans une session Grist active : il expire automatiquement après quelques minutes.


Et l’URL du proxy, est-elle un risque ?

L’URL du proxy apparaîtra dans les devtools au moment d’un upload.
C’est inévitable, toute requête réseau est visible. Cependant trois protections sont en place :

  • Vérification d’origine (CORS) : le proxy refuse toute requête dont l’Origin ne correspond pas à vos domaines autorisés. Depuis un navigateur, ce header est imposé par le navigateur lui-même — impossible à falsifier par du JavaScript.
  • Authentification par token Grist : le widget envoie automatiquement un token court-lived généré par grist.getAccessToken(). Le proxy le vérifie en temps réel contre votre instance Grist (/api/profile). Seul un utilisateur réellement connecté à votre Grist peut uploader.
  • Rate limiting : limite configurable de requêtes par IP et par minute, intégrée au proxy.

Pour utiliser le proxy sans Grist instance (usage public), la vérification du token peut être désactivée en laissant GRIST_URL vide dans la configuration .
Les deux premières protections restent actives.


Tutoriel d’installation du proxy

Petite précision importante avant de commencer :

Deux services distincts sont impliqués, ne pas les confondre :

  • Cloudflare Workers → c’est là où tourne le proxy (l’intermédiaire sécurisé). Il ne stocke aucun fichier, il fait juste le lien entre le widget et votre service de stockage. C’est gratuit jusqu’à 100 000 requêtes/jour, aucun serveur à maintenir.
  • Cloudinary (ou Nextcloud, S3, Supabase…) → c’est là où vos fichiers sont réellement stockés. C’est votre service de stockage habituel.

Le proxy Cloudflare reçoit le fichier du widget, l’envoie à Cloudinary avec vos credentials (stockés côté Cloudflare, jamais dans le navigateur), et vous retourne l’URL publique du fichier.


Étape 1 - Créer un compte Cloudflarehttps://dash.cloudflare.com/sign-up (email suffit)

Étape 2 - Récupérer le code du proxy

git clone https://github.com/isaytoo/grist-upload-proxy.git
cd grist-upload-proxy

Étape 3 - Installer Wrangler (l’outil de déploiement Cloudflare)

npm install -g wrangler
wrangler login

(une fenêtre s’ouvre dans le navigateur pour autoriser)

Étape 4 - Configurer wrangler.toml

Ouvrez le fichier wrangler.toml et ajustez ces lignes :

# Provider que vous utilisez : cloudinary | nextcloud | s3 | supabase
PROVIDER = "cloudinary"

# Ajoutez votre instance Grist self-hosted si nécessaire
# *.github.io et *.getgrist.com sont déjà inclus par défaut
ALLOWED_ORIGINS = "*.github.io,*.getgrist.com,https://votre-grist.exemple.fr"

# URL de votre instance Grist (active la vérification du token utilisateur)
# Laisser vide pour désactiver
GRIST_URL = "https://votre-grist.exemple.fr"

Étape 5 - Déployer

wrangler deploy

→ Vous obtenez une URL du type https://grist-upload-proxy.votresubdomain.workers.dev

Étape 6 - Renseigner vos credentials Cloudinary (une seule fois)

Les credentials sont stockés comme secrets chiffrés sur Cloudflare, jamais dans le code.

Ouvrez votre Terminal :

  • Mac : Cmd + Espace → tapez « Terminal » → Entrée
  • Windows : Win + R → tapez cmd → Entrée (ou recherchez « PowerShell » dans le menu Démarrer)

Assurez-vous d’être dans le dossier grist-upload-proxy :

cd chemin/vers/grist-upload-proxy

Puis lancez :

wrangler secret put CLOUDINARY_CLOUD_NAME

→ Wrangler affiche Enter a secret value: → tapez votre Cloud Name → Entrée

wrangler secret put CLOUDINARY_UPLOAD_PRESET

→ Wrangler affiche Enter a secret value: → tapez votre Upload Preset → Entrée

Le Cloud Name est visible sur votre dashboard Cloudinary en haut à gauche. L’Upload Preset se trouve dans Cloudinary → Settings → Upload → Upload presets. Il doit être en mode Unsigned.

Puis redéployez :

wrangler deploy

Étape 7 - Configurer le widget Grist

Dans votre document Grist :

  1. Widget Upload → :gear: Configurer
  2. Provider → :locked: Proxy sécurisé
  3. URL du proxy → https://grist-upload-proxy.votresubdomain.workers.dev
  4. Enregistrer

C’est prêt.


Autres providers supportés

Le proxy supporte également Nextcloud, AWS S3 / compatible (R2, B2, MinIO) et Supabase. Il suffit de changer PROVIDER dans wrangler.toml et de renseigner les secrets correspondants. La liste complète est documentée dans le fichier wrangler.toml du dépôt.


Utiliser un proxy existant ou un autre service

Le widget « Proxy sécurisé » accepte n’importe quel endpoint POST qui reçoit un fichier en multipart/form-data et retourne un JSON { "url": "..." }. Vous n’êtes pas obligé d’utiliser Cloudflare Workers, vous pouvez utiliser :

  • n8n : un webhook qui reçoit le fichier et l’envoie vers votre stockage
  • Vercel / Netlify Functions : une serverless function
  • Votre propre serveur : n’importe quel backend (Node.js, Python, PHP…)

Du moment que votre proxy respecte ce format de réponse et autorise l’origine de votre widget en CORS, il fonctionnera avec le widget.


Si vous changez de provider plus tard

Modifiez PROVIDER dans wrangler.toml, renseignez les nouveaux secrets, et relancez wrangler deploy. Aucune modification du widget nécessaire.


Code source du widget upload : https://github.com/isaytoo/grist-upload-widget
Code source du proxy sécurisé : https://github.com/isaytoo/grist-upload-proxy
Code source du widget media viewer : https://github.com/isaytoo/grist-media-viewer-widget

image