summaryrefslogtreecommitdiff
path: root/viewer-bff/public
diff options
context:
space:
mode:
Diffstat (limited to 'viewer-bff/public')
-rw-r--r--viewer-bff/public/app.js122
-rw-r--r--viewer-bff/public/index.html43
-rw-r--r--viewer-bff/public/styles.css93
3 files changed, 258 insertions, 0 deletions
diff --git a/viewer-bff/public/app.js b/viewer-bff/public/app.js
new file mode 100644
index 0000000..ecbe3fb
--- /dev/null
+++ b/viewer-bff/public/app.js
@@ -0,0 +1,122 @@
+const tokenInput = document.getElementById("tokenInput");
+const objectKeysInput = document.getElementById("objectKeysInput");
+const permissionsOutput = document.getElementById("permissionsOutput");
+const gallery = document.getElementById("gallery");
+
+const loadPermissionsBtn = document.getElementById("loadPermissionsBtn");
+const buildGalleryBtn = document.getElementById("buildGalleryBtn");
+
+let currentPermissions = null;
+
+function getToken() {
+ return tokenInput.value.trim();
+}
+
+function parseObjectKeys() {
+ return objectKeysInput.value
+ .split("\n")
+ .map((line) => line.trim())
+ .filter(Boolean);
+}
+
+function isAllowedByPermissions(objectKey, permissions) {
+ if (!permissions) return false;
+ if (permissions.allowAll) return true;
+ const prefixes = permissions.allowedPrefixes || [];
+ return prefixes.some((prefix) => objectKey.startsWith(prefix));
+}
+
+async function callJson(url, options = {}) {
+ const token = getToken();
+ const headers = {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ };
+ if (token) {
+ headers.Authorization = `Bearer ${token}`;
+ }
+
+ const response = await fetch(url, { ...options, headers });
+ const payload = await response.json().catch(() => ({}));
+ if (!response.ok) {
+ throw new Error(payload.message || `HTTP ${response.status}`);
+ }
+ return payload;
+}
+
+loadPermissionsBtn.addEventListener("click", async () => {
+ try {
+ const perms = await callJson("/api/me/permissions");
+ currentPermissions = perms;
+ permissionsOutput.textContent = JSON.stringify(perms, null, 2);
+ } catch (error) {
+ permissionsOutput.textContent = `Erreur: ${error.message}`;
+ }
+});
+
+buildGalleryBtn.addEventListener("click", async () => {
+ gallery.innerHTML = "";
+ const keys = parseObjectKeys();
+
+ if (!currentPermissions) {
+ permissionsOutput.textContent = "Charger d'abord les permissions.";
+ return;
+ }
+
+ if (!keys.length) {
+ permissionsOutput.textContent = "Ajouter au moins une objectKey.";
+ return;
+ }
+
+ for (const objectKey of keys) {
+ const card = document.createElement("article");
+ card.className = "card";
+
+ const img = document.createElement("img");
+ img.className = "thumb";
+ img.alt = objectKey;
+
+ const keyP = document.createElement("p");
+ keyP.className = "key";
+ keyP.textContent = objectKey;
+
+ const openBtn = document.createElement("button");
+ openBtn.textContent = "Ouvrir";
+ openBtn.disabled = !isAllowedByPermissions(objectKey, currentPermissions);
+
+ openBtn.addEventListener("click", async () => {
+ try {
+ const presign = await callJson("/api/media/presign", {
+ method: "POST",
+ body: JSON.stringify({ objectKey })
+ });
+ const signedUrl = presign.url;
+ img.src = signedUrl;
+ window.open(signedUrl, "_blank", "noopener,noreferrer");
+ } catch (error) {
+ alert(`Presign refuse: ${error.message}`);
+ }
+ });
+
+ if (!openBtn.disabled) {
+ // Previsualisation opportuniste pour les objets autorises.
+ callJson("/api/media/presign", {
+ method: "POST",
+ body: JSON.stringify({ objectKey })
+ })
+ .then((presign) => {
+ img.src = presign.url;
+ })
+ .catch(() => {
+ img.alt = "Previsualisation indisponible";
+ });
+ } else {
+ img.alt = "Acces refuse (roles)";
+ }
+
+ card.appendChild(img);
+ card.appendChild(keyP);
+ card.appendChild(openBtn);
+ gallery.appendChild(card);
+ }
+});
diff --git a/viewer-bff/public/index.html b/viewer-bff/public/index.html
new file mode 100644
index 0000000..8112bfa
--- /dev/null
+++ b/viewer-bff/public/index.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<html lang="fr">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Viewer Medias (POC)</title>
+ <link rel="stylesheet" href="/styles.css" />
+ </head>
+ <body>
+ <main class="container">
+ <h1>Viewer medias securise (POC)</h1>
+
+ <section class="panel">
+ <h2>1) Token utilisateur</h2>
+ <textarea
+ id="tokenInput"
+ placeholder="Coller ici le JWT (Bearer token)"
+ rows="5"
+ ></textarea>
+ <button id="loadPermissionsBtn">Charger permissions</button>
+ <pre id="permissionsOutput">Aucune permission chargee.</pre>
+ </section>
+
+ <section class="panel">
+ <h2>2) Cles objet a visualiser</h2>
+ <p class="hint">
+ Une cle par ligne, exemple: <code>photos/equipeA/2026/03/img001.jpg</code>
+ </p>
+ <textarea id="objectKeysInput" rows="8"></textarea>
+ <div class="actions">
+ <button id="buildGalleryBtn">Construire la galerie</button>
+ </div>
+ </section>
+
+ <section class="panel">
+ <h2>3) Galerie</h2>
+ <div id="gallery" class="gallery"></div>
+ </section>
+ </main>
+
+ <script src="/app.js"></script>
+ </body>
+</html>
diff --git a/viewer-bff/public/styles.css b/viewer-bff/public/styles.css
new file mode 100644
index 0000000..5417318
--- /dev/null
+++ b/viewer-bff/public/styles.css
@@ -0,0 +1,93 @@
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ font-family: Arial, sans-serif;
+ background: #0f172a;
+ color: #e2e8f0;
+}
+
+.container {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 24px;
+}
+
+h1 {
+ margin: 0 0 20px;
+}
+
+.panel {
+ background: #111827;
+ border: 1px solid #334155;
+ border-radius: 8px;
+ padding: 16px;
+ margin-bottom: 16px;
+}
+
+textarea {
+ width: 100%;
+ background: #0b1220;
+ color: #e2e8f0;
+ border: 1px solid #334155;
+ border-radius: 6px;
+ padding: 10px;
+}
+
+button {
+ margin-top: 10px;
+ background: #2563eb;
+ border: none;
+ color: white;
+ border-radius: 6px;
+ padding: 8px 12px;
+ cursor: pointer;
+}
+
+button:hover {
+ background: #1d4ed8;
+}
+
+.hint {
+ margin-top: 0;
+ color: #94a3b8;
+}
+
+#permissionsOutput {
+ white-space: pre-wrap;
+ background: #0b1220;
+ border: 1px solid #334155;
+ border-radius: 6px;
+ padding: 10px;
+ min-height: 50px;
+}
+
+.gallery {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 14px;
+}
+
+.card {
+ background: #0b1220;
+ border: 1px solid #334155;
+ border-radius: 8px;
+ padding: 10px;
+}
+
+.thumb {
+ width: 100%;
+ height: 140px;
+ object-fit: cover;
+ border-radius: 6px;
+ background: #1e293b;
+}
+
+.key {
+ margin: 8px 0;
+ font-size: 12px;
+ word-break: break-all;
+ color: #cbd5e1;
+}