ernestmindres commited on
Commit
8263001
·
verified ·
1 Parent(s): 6c21a43

Update templates/api_key.html

Browse files
Files changed (1) hide show
  1. templates/api_key.html +181 -153
templates/api_key.html CHANGED
@@ -142,7 +142,7 @@
142
 
143
  <body class="bg-page-var text-color-var min-h-screen transition-colors-theme">
144
 
145
- <button id="theme-toggle" class="theme-switch text-color-var hover:text-link-hover-var transition-colors-theme">
146
  <span class="material-symbols-rounded">dark_mode</span>
147
  </button>
148
 
@@ -157,7 +157,7 @@
157
  <span class="material-symbols-rounded">dashboard</span>
158
  <span class="ml-3">Dashboard</span>
159
  </a>
160
- <a href="{{ url_for('user_bp.api_key') }}" class="flex items-center p-3 bg-sidebar-active-var text-color-var rounded-lg transition duration-150 transition-colors-theme">
161
  <span class="material-symbols-rounded">key</span>
162
  <span class="ml-3">Clés API</span>
163
  </a>
@@ -172,8 +172,8 @@
172
  </nav>
173
 
174
  <div class="absolute bottom-6 w-52 text-xs text-secondary-var transition-colors-theme">
175
- <p>Connecté en tant que: <span class="font-semibold text-color-var transition-colors-theme">{{ user.email if user else 'Chargement...' }}</span></p>
176
- <p>Plan: <span class="font-semibold text-color-var transition-colors-theme">{{ (user.plan | upper) if user else 'Chargement...' }}</span></p>
177
  </div>
178
 
179
  </aside>
@@ -210,15 +210,19 @@
210
  <span class="material-symbols-rounded mr-2">warning</span>
211
  Attention Sécurité
212
  </h3>
213
- <p class="text-sm mt-2">Chacune de vos **5 clés API** est un mot de passe pour vos applications. Ne les partagez jamais publiquement et traitez-les comme des informations confidentielles. Régénérez immédiatement une clé si vous suspectez un risque.</p>
214
  </div>
 
215
  <div class="bg-card-var p-6 rounded-lg shadow-xl border border-sidebar-var transition-colors-theme">
216
  <h2 class="text-xl font-semibold mb-4 text-color-var transition-colors-theme">Vos Clés d'Accès API (5 Clés Disponibles)</h2>
217
  <p class="text-secondary-var mb-6 transition-colors-theme">Chaque clé peut être utilisée pour authentifier vos requêtes auprès de l'API Nexus. Régénérez une clé si elle est compromise.</p>
218
 
219
- {% for index in range(5) %}
220
- {% set api_key_value = user.api_keys[index] if user and user.api_keys and user.api_keys|length > index else 'NXS_API_KEY_' ~ (index + 1) ~ '_NON_DISPONIBLE' %}
221
 
 
 
 
 
222
  <div id="key-block-{{ index }}" class="key-block space-y-4 mb-4 p-4 border border-input-var rounded-lg bg-input-var/50 transition-colors-theme">
223
  <h3 class="text-lg font-medium text-color-var transition-colors-theme">Clé API n°{{ index + 1 }}</h3>
224
 
@@ -227,16 +231,17 @@
227
  type="text"
228
  readonly
229
  class="api-key-input flex-grow bg-input-var border border-input-var text-input-key-var p-3 rounded-lg font-mono text-sm break-all focus:outline-none focus:ring-2 focus:ring-primary-color-var focus:border-primary-color-var transition-colors-theme"
230
- value="{{ api_key_value }}">
231
 
232
- <button data-key-index="{{ index }}" class="copy-btn bg-primary-color-var hover:bg-primary-hover-var text-white font-bold py-3 px-4 rounded-lg transition duration-200 flex items-center btn-square transition-colors-theme">
 
233
  <span class="material-symbols-rounded">content_copy</span>
234
  <span class="ml-2 hidden sm:inline">Copier</span>
235
  </button>
236
  </div>
237
 
238
  <div class="flex items-center justify-between border-t border-input-var pt-4 transition-colors-theme">
239
- <button data-key-index="{{ index }}" class="regenerate-btn text-sm text-red-500 hover:text-red-400 transition duration-200 flex items-center font-medium transition-colors-theme">
240
  <span class="material-symbols-rounded text-lg mr-1">refresh</span>
241
  Régénérer cette Clé
242
  <div id="spinner-{{ index }}" class="spinner w-4 h-4 border-2 rounded-full ml-2 hidden" style="border-bottom-color: transparent;"></div>
@@ -245,163 +250,186 @@
245
  </div>
246
  </div>
247
  {% endfor %}
248
- </div>
 
249
  </div>
250
 
251
- <script>
252
- // Logique du thème (conservée)
253
- const themeToggle = document.getElementById('theme-toggle');
254
- const html = document.documentElement;
255
-
256
- const updateIcon = () => {
257
- const icon = themeToggle.querySelector('.material-symbols-rounded');
258
- if (html.classList.contains('light')) {
259
- icon.textContent = 'light_mode';
260
- } else {
261
- icon.textContent = 'dark_mode';
262
- }
263
- };
264
 
265
- const savedTheme = localStorage.getItem('theme');
266
- if (savedTheme === 'light') {
267
- html.classList.remove('dark');
268
- html.classList.add('light');
 
 
 
 
269
  } else {
270
- // Par défaut, nous nous assurons que 'dark' est appliqué s'il n'y a pas de thème enregistré
271
- html.classList.add('dark');
272
- html.classList.remove('light');
 
273
  }
274
- updateIcon();
275
-
276
- themeToggle.addEventListener('click', () => {
277
- if (html.classList.contains('dark')) {
278
- html.classList.remove('dark');
279
- html.classList.add('light');
280
- localStorage.setItem('theme', 'light');
281
- } else {
282
- html.classList.remove('light');
283
- html.classList.add('dark');
284
- localStorage.setItem('theme', 'dark');
285
- }
286
- updateIcon();
 
 
 
 
 
 
 
 
 
 
 
 
287
  });
 
288
 
 
289
 
290
- // 1. Logique de la carte d'avertissement masquable
291
- const warningCard = document.getElementById('security-warning-card');
292
- const dismissButton = document.getElementById('dismiss-warning');
 
 
293
 
294
- // Vérifier le statut dans le localStorage au chargement
295
- if (localStorage.getItem('api_key_warning_dismissed') === 'true') {
296
- if (warningCard) {
297
- warningCard.style.display = 'none';
298
- }
 
299
  }
300
 
301
- // Gérer le clic sur le bouton de fermeture
302
- if (dismissButton) {
303
- dismissButton.addEventListener('click', () => {
304
- if (warningCard) {
305
- warningCard.style.display = 'none';
306
- // Sauvegarder l'état dans le localStorage
307
- localStorage.setItem('api_key_warning_dismissed', 'true');
308
- }
309
- });
310
- }
311
-
312
- // 2. Logique pour la copie des clés API - MISE À JOUR pour 5 clés (fonctionne car sélecteurs génériques)
313
- document.querySelectorAll('.copy-btn').forEach(button => {
314
- button.addEventListener('click', (e) => {
315
- const index = e.currentTarget.getAttribute('data-key-index');
316
- const apiKeyDisplay = document.getElementById(`api-key-display-${index}`);
317
- const statusMessage = document.getElementById(`status-message-${index}`);
318
-
319
- if (apiKeyDisplay) {
320
- // Pour le mode de démo, on s'assure que le contenu est 'sélectionnable' même s'il est readonly
321
- apiKeyDisplay.select();
322
- apiKeyDisplay.setSelectionRange(0, 99999); /* Pour les mobiles */
323
- // L'utilisation de document.execCommand('copy') est obsolète mais souvent supportée.
324
- // Pour une meilleure compatibilité future, on pourrait utiliser l'API Clipboard
325
- navigator.clipboard.writeText(apiKeyDisplay.value).then(() => {
326
- statusMessage.textContent = 'Clé copiée !';
327
- setTimeout(() => {
328
- statusMessage.textContent = '';
329
- }, 3000);
330
- }).catch(err => {
331
- // Fallback pour les anciens navigateurs
332
- document.execCommand('copy');
333
- statusMessage.textContent = 'Clé copiée (via fallback) !';
334
- setTimeout(() => {
335
- statusMessage.textContent = '';
336
- }, 3000);
337
- });
338
- }
339
- });
340
  });
341
-
342
- // 3. Logique de la Sidebar sur mobile (conservée)
343
- const sidebarToggle = document.getElementById('sidebar-toggle');
344
- const sidebar = document.getElementById('sidebar');
345
-
346
- sidebarToggle.addEventListener('click', () => {
347
- sidebar.classList.toggle('open');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  });
349
-
350
- // 4. Logique de REGÉNÉRATION (Message de démo) - MISE À JOUR pour 5 clés (fonctionne car sélecteurs génériques)
351
- document.querySelectorAll('.regenerate-btn').forEach(button => {
352
- button.addEventListener('click', (e) => {
353
- e.preventDefault();
354
- const index = e.currentTarget.getAttribute('data-key-index');
355
- const statusMessage = document.getElementById(`status-message-${index}`);
356
- const spinner = document.getElementById(`spinner-${index}`);
357
-
358
- // Pour la prochaine étape (backend), ce code sera mis à jour avec un appel fetch()
359
-
360
- // Démarrer le spinner
361
- spinner.classList.remove('hidden');
362
-
363
- // Simuler une action
364
- statusMessage.textContent = `Régénération de la Clé n°${parseInt(index) + 1} en cours...`;
365
-
366
- // Ici, il faudra un appel `fetch` vers POST /api/regenerate-api-key
367
- // Exemple (Futur Code) :
368
- /* fetch('/api/regenerate-api-key', {
 
 
369
  method: 'POST',
370
  headers: { 'Content-Type': 'application/json' },
371
- body: JSON.stringify({ key_index: parseInt(index) }) // 0, 1, 2, 3, 4
372
- })
373
- .then(response => response.json())
374
- .then(data => {
375
- // MAJ du champ avec data.new_key
376
- // ...
377
- })
378
- .finally(() => {
379
- spinner.classList.add('hidden');
380
- // ...
381
  });
382
- */
383
-
384
- // Message de démo temporaire
385
- setTimeout(() => {
386
- statusMessage.textContent = 'Fonction de régénération en cours de développement (OK pour le Front-end).';
387
- spinner.classList.add('hidden');
388
- setTimeout(() => {
389
- statusMessage.textContent = '';
390
- }, 4000);
391
- }, 2000);
392
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  });
394
-
395
- // 5. Logique de Déconnexion (Conservée)
396
- const logoutHandler = async (e) => {
397
- e.preventDefault();
398
- // La déconnexion est maintenant gérée par la route Flask directe
399
- window.location.href = document.getElementById('logout-button-sidebar').href;
400
- };
401
- // On attache l'événement à la déconnexion dans la sidebar.
402
- document.getElementById('logout-button-sidebar').addEventListener('click', logoutHandler);
403
-
404
-
405
- </script>
406
- </body>
407
- </html>
 
142
 
143
  <body class="bg-page-var text-color-var min-h-screen transition-colors-theme">
144
 
145
+ <button id="theme-switch" class="theme-switch text-color-var hover:text-link-hover-var transition-colors-theme">
146
  <span class="material-symbols-rounded">dark_mode</span>
147
  </button>
148
 
 
157
  <span class="material-symbols-rounded">dashboard</span>
158
  <span class="ml-3">Dashboard</span>
159
  </a>
160
+ <a href="{{ url_for('user_bp.cle_api') }}" class="flex items-center p-3 bg-sidebar-active-var text-color-var rounded-lg transition duration-150 transition-colors-theme">
161
  <span class="material-symbols-rounded">key</span>
162
  <span class="ml-3">Clés API</span>
163
  </a>
 
172
  </nav>
173
 
174
  <div class="absolute bottom-6 w-52 text-xs text-secondary-var transition-colors-theme">
175
+ <p>Connecté en tant que: <span class="font-semibold text-color-var transition-colors-theme">{{ user.email if user else 'Chargement...' }}</span></p>
176
+ <p>Plan: <span class="font-semibold text-color-var transition-colors-theme">{{ (user.plan | upper) if user else 'Chargement...' }}</span></p>
177
  </div>
178
 
179
  </aside>
 
210
  <span class="material-symbols-rounded mr-2">warning</span>
211
  Attention Sécurité
212
  </h3>
213
+ <p class="text-sm mt-2">Chacune de vos 5 clés API est un mot de passe pour vos applications. Ne les partagez jamais publiquement et traitez-les comme des informations confidentielles. Régénérez immédiatement une clé si vous suspectez un risque.</p>
214
  </div>
215
+
216
  <div class="bg-card-var p-6 rounded-lg shadow-xl border border-sidebar-var transition-colors-theme">
217
  <h2 class="text-xl font-semibold mb-4 text-color-var transition-colors-theme">Vos Clés d'Accès API (5 Clés Disponibles)</h2>
218
  <p class="text-secondary-var mb-6 transition-colors-theme">Chaque clé peut être utilisée pour authentifier vos requêtes auprès de l'API Nexus. Régénérez une clé si elle est compromise.</p>
219
 
220
+ {% set api_keys = [user.get('api_key'), user.get('api_key_2'), user.get('api_key_3'), user.get('api_key_4'), user.get('api_key_5')] %}
 
221
 
222
+ {% for key_value in api_keys %}
223
+ {% set index = loop.index0 %}
224
+ {% set display_value = key_value if key_value else 'Non générée (ou plan Free)' %}
225
+
226
  <div id="key-block-{{ index }}" class="key-block space-y-4 mb-4 p-4 border border-input-var rounded-lg bg-input-var/50 transition-colors-theme">
227
  <h3 class="text-lg font-medium text-color-var transition-colors-theme">Clé API n°{{ index + 1 }}</h3>
228
 
 
231
  type="text"
232
  readonly
233
  class="api-key-input flex-grow bg-input-var border border-input-var text-input-key-var p-3 rounded-lg font-mono text-sm break-all focus:outline-none focus:ring-2 focus:ring-primary-color-var focus:border-primary-color-var transition-colors-theme"
234
+ value="{{ display_value }}">
235
 
236
+ <button data-key-index="{{ index }}" class="copy-btn bg-primary-color-var hover:bg-primary-hover-var text-white font-bold py-3 px-4 rounded-lg transition duration-200 flex items-center btn-square transition-colors-theme"
237
+ data-clipboard-target="#api-key-display-{{ index }}">
238
  <span class="material-symbols-rounded">content_copy</span>
239
  <span class="ml-2 hidden sm:inline">Copier</span>
240
  </button>
241
  </div>
242
 
243
  <div class="flex items-center justify-between border-t border-input-var pt-4 transition-colors-theme">
244
+ <button data-key-index="{{ index }}" class="regenerate-btn text-sm text-red-500 hover:text-red-400 transition duration-200 flex items-center font-medium transition-colors-theme" {% if not key_value %}disabled{% endif %}>
245
  <span class="material-symbols-rounded text-lg mr-1">refresh</span>
246
  Régénérer cette Clé
247
  <div id="spinner-{{ index }}" class="spinner w-4 h-4 border-2 rounded-full ml-2 hidden" style="border-bottom-color: transparent;"></div>
 
250
  </div>
251
  </div>
252
  {% endfor %}
253
+
254
+ </div>
255
  </div>
256
 
257
+ <script>
258
+ // 1. Logique du mode sombre/clair (CORRIGÉE)
259
+ const themeSwitch = document.getElementById('theme-switch');
260
+ const htmlElement = document.documentElement; // Référence à l'élément <html>
 
 
 
 
 
 
 
 
 
261
 
262
+ // Fonction pour appliquer le thème
263
+ function applyTheme(theme) {
264
+ // Ajoute la classe 'light' si le thème est 'light', sinon ajoute 'dark'
265
+ if (theme === 'light') {
266
+ htmlElement.classList.add('light');
267
+ htmlElement.classList.remove('dark');
268
+ // MÀJ de l'icône vers 'dark_mode' (lune) quand le mode est 'light'
269
+ themeSwitch.innerHTML = '<span class="material-symbols-rounded">dark_mode</span>';
270
  } else {
271
+ htmlElement.classList.add('dark');
272
+ htmlElement.classList.remove('light');
273
+ // MÀJ de l'icône vers 'light_mode' (soleil) quand le mode est 'dark'
274
+ themeSwitch.innerHTML = '<span class="material-symbols-rounded">light_mode</span>';
275
  }
276
+ localStorage.setItem('theme', theme);
277
+ }
278
+
279
+ // Initialisation du thème
280
+ // Le thème par défaut est 'dark' si localStorage est vide ou si le système préfère le dark
281
+ const savedTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
282
+ applyTheme(savedTheme);
283
+
284
+ // Événement de bascule
285
+ themeSwitch.addEventListener('click', () => {
286
+ // Détermine le nouveau thème basé sur la présence de la classe 'dark' sur <html>
287
+ const currentTheme = htmlElement.classList.contains('dark') ? 'dark' : 'light';
288
+ const newTheme = currentTheme === 'light' ? 'dark' : 'light';
289
+ applyTheme(newTheme);
290
+ });
291
+
292
+ // Logique de bascule de la sidebar pour mobile/tablette (CORRIGÉE : Ajout de la bascule 'open')
293
+ const sidebarToggle = document.getElementById('sidebar-toggle');
294
+ const sidebar = document.getElementById('sidebar');
295
+ // Le mainContent n'a pas besoin de la classe 'sidebar-open' pour le style actuel
296
+ // const mainContent = document.getElementById('main-content');
297
+
298
+ if (sidebarToggle && sidebar) {
299
+ sidebarToggle.addEventListener('click', () => {
300
+ sidebar.classList.toggle('open'); // Bascule de la classe 'open'
301
  });
302
+ }
303
 
304
+ // FIN de la Logique du mode sombre/clair et Sidebar
305
 
306
+ // --- Logique d'affichage et d'interaction des Clés API (MISE À JOUR) ---
307
+
308
+ // 2. Logique d'avertissement de sécurité (conservée)
309
+ const warningCard = document.getElementById('security-warning-card');
310
+ const dismissButton = document.getElementById('dismiss-warning');
311
 
312
+ if (warningCard && dismissButton) {
313
+ // Afficher si non masqué précédemment
314
+ if (localStorage.getItem('dismissed_warning') !== 'true') {
315
+ warningCard.style.display = 'block';
316
+ } else {
317
+ warningCard.style.display = 'none';
318
  }
319
 
320
+ dismissButton.addEventListener('click', () => {
321
+ warningCard.style.display = 'none';
322
+ // Stocker dans le localStorage pour masquer lors des prochaines visites
323
+ localStorage.setItem('dismissed_warning', 'true');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  });
325
+ }
326
+
327
+
328
+ // 3. Logique de Copie (MISE À JOUR)
329
+ const copyButtons = document.querySelectorAll('.copy-btn');
330
+ copyButtons.forEach(button => {
331
+ button.addEventListener('click', () => {
332
+ // Récupère l'ID de l'input à copier
333
+ const targetId = button.getAttribute('data-clipboard-target').substring(1);
334
+ const apiKeyInput = document.getElementById(targetId);
335
+ const apiKey = apiKeyInput.value.trim();
336
+ const index = button.getAttribute('data-key-index');
337
+ const statusMessage = document.getElementById(`status-message-${index}`);
338
+
339
+ if (apiKey === 'Non générée (ou plan Free)' || apiKey.includes('NON_DISPONIBLE')) {
340
+ statusMessage.textContent = 'Erreur: Aucune clé à copier.';
341
+ setTimeout(() => { statusMessage.textContent = ''; }, 3000);
342
+ return;
343
+ }
344
+
345
+ // Copie au presse-papiers
346
+ navigator.clipboard.writeText(apiKey).then(() => {
347
+ statusMessage.textContent = 'Clé API copiée !';
348
+ statusMessage.classList.add('text-green-500');
349
+ setTimeout(() => {
350
+ statusMessage.textContent = '';
351
+ statusMessage.classList.remove('text-green-500');
352
+ }, 3000);
353
+ }).catch(err => {
354
+ statusMessage.textContent = 'Échec de la copie.';
355
+ statusMessage.classList.add('text-red-500');
356
+ console.error('Échec de la copie:', err);
357
+ setTimeout(() => {
358
+ statusMessage.textContent = '';
359
+ statusMessage.classList.remove('text-red-500');
360
+ }, 3000);
361
+ });
362
  });
363
+ });
364
+
365
+ // 4. Logique de Régénération (MISE À JOUR)
366
+ const regenerateButtons = document.querySelectorAll('.regenerate-btn');
367
+ regenerateButtons.forEach(button => {
368
+ button.addEventListener('click', async (e) => {
369
+ e.preventDefault();
370
+ const index = button.getAttribute('data-key-index'); // Récupère l'index (0 à 4)
371
+ const apiKeyInput = document.getElementById(`api-key-display-${index}`);
372
+ const statusMessage = document.getElementById(`status-message-${index}`);
373
+ const spinner = document.getElementById(`spinner-${index}`);
374
+
375
+ // Affichage de l'indicateur de chargement
376
+ statusMessage.textContent = 'Régénération en cours...';
377
+ statusMessage.classList.remove('text-green-500', 'text-red-500');
378
+ statusMessage.classList.add('text-input-key-var');
379
+ spinner.classList.remove('hidden');
380
+ button.disabled = true;
381
+
382
+ try {
383
+ // Envoi de l'index de la clé (0 à 4) au backend Flask
384
+ const response = await fetch('/api/regenerate-api-key', {
385
  method: 'POST',
386
  headers: { 'Content-Type': 'application/json' },
387
+ body: JSON.stringify({ key_index: parseInt(index) })
 
 
 
 
 
 
 
 
 
388
  });
389
+
390
+ const data = await response.json();
391
+
392
+ if (data.success) {
393
+ // MÀJ de la valeur affichée avec la nouvelle clé
394
+ apiKeyInput.value = data.new_key;
395
+ statusMessage.textContent = data.message;
396
+ statusMessage.classList.add('text-green-500');
397
+ button.disabled = false; // Réactive le bouton
398
+ } else {
399
+ statusMessage.textContent = `Erreur: ${data.message}`;
400
+ statusMessage.classList.add('text-red-500');
401
+ // Le bouton reste désactivé ou est réactivé selon l'erreur
402
+ if (apiKeyInput.value.includes('NON_DISPONIBLE')) {
403
+ button.disabled = true;
404
+ } else {
405
+ button.disabled = false;
406
+ }
407
+ }
408
+
409
+ } catch (error) {
410
+ statusMessage.textContent = 'Erreur réseau lors de la régénération.';
411
+ statusMessage.classList.add('text-red-500');
412
+ console.error('Erreur de régénération:', error);
413
+ button.disabled = false;
414
+ } finally {
415
+ spinner.classList.add('hidden');
416
+ // Nettoyage du message de statut après un délai
417
+ setTimeout(() => {
418
+ statusMessage.textContent = '';
419
+ statusMessage.classList.remove('text-green-500', 'text-red-500', 'text-input-key-var');
420
+ }, 4000);
421
+ }
422
  });
423
+ });
424
+
425
+ // 5. Logique de Déconnexion (Conservée)
426
+ const logoutHandler = async (e) => {
427
+ e.preventDefault();
428
+ // La déconnexion est maintenant gérée par la route Flask directe
429
+ window.location.href = document.getElementById('logout-button-sidebar').href;
430
+ };
431
+ // On attache l'événement à la déconnexion dans la sidebar.
432
+ document.getElementById('logout-button-sidebar').addEventListener('click', logoutHandler);
433
+
434
+
435
+ </script>