ernestmindres commited on
Commit
633257d
·
verified ·
1 Parent(s): 38c8bce

Update templates/static_deploy.html

Browse files
Files changed (1) hide show
  1. templates/static_deploy.html +379 -123
templates/static_deploy.html CHANGED
@@ -107,78 +107,148 @@
107
  @keyframes spinner-border {
108
  to { transform: rotate(360deg); }
109
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  </style>
111
 
 
 
 
 
 
 
112
  <input type="file" id="file-input" multiple style="display: none;">
113
 
114
 
115
- <div class="container my-5">
116
- <div class="row">
117
- <div class="col-12 col-lg-8 mx-auto">
118
- <h2 class="text-center mb-4"> Déploiement Statique Instantané</h2>
119
-
120
- <div id="drop-zone" class="dropzone">
121
- <span class="material-symbols-rounded dropzone-icon">cloud_upload</span>
122
- <p class="dropzone-text">Glissez et déposez vos fichiers ici (HTML, CSS, JS, Images)</p>
123
- <p class="dropzone-hint">Ou cliquez pour sélectionner des fichiers. Un fichier index.html est requis.</p>
124
- <button id="select-files-button" class="btn btn-outline-primary mt-3">Sélectionner des Fichiers</button>
125
- </div>
126
 
127
- <div id="file-upload-status" class="mt-3 text-muted">
128
- Aucun fichier téléversé.
129
- </div>
130
 
131
- <ul id="uploaded-file-list" class="file-list p-3 border rounded shadow-sm bg-white" style="display: none;">
132
- </ul>
133
-
134
- <div id="action-zone" class="mt-4 p-4 border rounded shadow-sm bg-white">
135
- <div class="d-flex justify-content-between align-items-center">
136
- <button id="launch-button" class="btn btn-info me-2" disabled>
137
- <span class="material-symbols-rounded align-middle me-1">open_in_new</span> Lancer l'Aperçu (Temp)
138
- </button>
139
-
140
- <button id="deploy-button" class="btn btn-success me-2" disabled>
141
- <span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)
142
- </button>
143
-
144
- <button id="clear-button" class="btn btn-danger" disabled>
145
- <span class="material-symbols-rounded align-middle me-1">delete_forever</span> Vider
146
- </button>
147
- </div>
148
  </div>
 
149
 
150
- <div id="permanent-url-container" class="mt-4 p-3 bg-success-light border rounded" style="display: none; background-color: #d1e7dd !important; border-color: #badbcc !important;">
151
- <label class="form-label fw-bold d-block text-success" style="color: #0f5132 !important;">
152
- <span class="material-symbols-rounded align-middle me-1">link</span> Lien Permanent du Site
153
- </label>
154
- <div class="input-group mb-2">
155
- <input type="text" id="permanent-url-display" class="form-control" readonly value="">
156
- <button class="btn btn-outline-secondary" type="button" id="copy-permanent-button" title="Copier le lien permanent">
157
- <span class="material-symbols-rounded">content_copy</span>
158
- </button>
159
- </div>
160
- <small class="text-muted mt-1 d-block" style="color: #0f5132 !important;"> ✅ Ce lien est sauvegardé sur Hugging Face et est permanent. </small>
161
  </div>
162
-
163
- <div id="temp-url-container" class="mt-4 p-3 bg-light border rounded" style="display: none; background-color: #fff3cd !important; border-color: #ffc107 !important;">
164
- <label class="form-label fw-bold d-block text-warning" style="color: #664d03 !important;">
165
- <span class="material-symbols-rounded align-middle me-1">share</span> Lien d'Aperçu Temporaire
166
- </label>
167
- <div class="input-group mb-2">
168
- <input type="text" id="temp-url-display" class="form-control" readonly value="{{ temp_launch_url if temp_launch_url else '' }}">
169
- <button class="btn btn-outline-secondary" type="button" id="copy-temp-button" title="Copier le lien temporaire">
170
- <span class="material-symbols-rounded">content_copy</span>
171
- </button>
172
- </div>
173
- <small class="text-muted mt-1 d-block"> ⚠️ Ce lien est temporaire. Cliquez sur "Déployer le Site Web" pour le rendre permanent. </small>
174
  </div>
175
-
176
  </div>
 
177
  </div>
178
  </div>
179
 
180
  <script>
181
  // Récupération des éléments du DOM
 
 
 
 
182
  const dropZone = document.getElementById('drop-zone');
183
  const fileInput = document.getElementById('file-input');
184
  const selectFilesButton = document.getElementById('select-files-button');
@@ -186,7 +256,7 @@ const uploadStatus = document.getElementById('file-upload-status');
186
  const uploadedFileList = document.getElementById('uploaded-file-list');
187
  const launchButton = document.getElementById('launch-button');
188
  const clearButton = document.getElementById('clear-button');
189
- const deployButton = document.getElementById('deploy-button'); // Le bouton Commit Change
190
 
191
  const permanentUrlContainer = document.getElementById('permanent-url-container');
192
  const permanentUrlInput = document.getElementById('permanent-url-display');
@@ -196,19 +266,24 @@ const tempUrlContainer = document.getElementById('temp-url-container');
196
  const tempUrlInput = document.getElementById('temp-url-display');
197
  const copyTempButton = document.getElementById('copy-temp-button');
198
 
 
 
199
  // Données injectées par Flask (Assurez-vous que votre route Flask passe ces variables)
200
- const currentTempUrl = '{{ temp_launch_url if temp_launch_url else "" }}';
201
- const currentPermanentUrl = '{{ permanent_deployment_url if permanent_deployment_url else "" }}';
202
- const indexFilePresent = {{ 'true' if 'index.html' in files else 'false' }};
203
- const fileCount = {{ files|length }};
204
  const deployId = '{{ deploy_id }}';
205
- const files = JSON.parse('{{ files | tojson | safe }}'); // Liste des fichiers pour l'affichage initial
 
 
 
206
 
207
  // -------------------------------------------------------------
208
  // --- Fonctions d'Utilitaires et de Mise à Jour de l'Interface ---
209
  // -------------------------------------------------------------
210
 
211
- /** Affiche la liste des fichiers téléversés */
212
  function renderFileList(fileList) {
213
  uploadedFileList.innerHTML = '';
214
  if (fileList.length === 0) {
@@ -224,41 +299,232 @@ function renderFileList(fileList) {
224
  uploadedFileList.style.display = 'block';
225
  }
226
 
227
- /** Met à jour l'état initial des éléments au chargement de la page */
228
- function updateInitialState() {
229
- renderFileList(files);
230
 
231
  if (currentPermanentUrl) {
232
  // État final: URL permanente
233
  permanentUrlInput.value = currentPermanentUrl;
234
  permanentUrlContainer.style.display = 'block';
 
 
235
  deployButton.disabled = true;
236
  deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_done</span> Déployé !';
237
- deployButton.classList.remove('btn-success');
238
- deployButton.classList.add('btn-secondary');
239
  launchButton.disabled = false;
240
- tempUrlContainer.style.display = 'none';
241
- } else if (currentTempUrl && indexFilePresent) {
 
 
 
242
  // État temporaire: prêt à être déployé
243
- tempUrlInput.value = currentTempUrl; // Assurer que l'input a la valeur
244
  tempUrlContainer.style.display = 'block';
 
 
245
  deployButton.disabled = false;
 
 
 
246
  launchButton.disabled = false;
247
- permanentUrlContainer.style.display = 'none';
 
 
 
248
  } else {
249
  // Aucun fichier ou index.html manquant
250
- deployButton.disabled = true;
251
- launchButton.disabled = true;
252
  tempUrlContainer.style.display = 'none';
253
  permanentUrlContainer.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
- clearButton.disabled = fileCount === 0;
257
- uploadStatus.textContent = fileCount > 0
258
- ? `Fichiers téléversés temporairement (${fileCount} fichiers. Index.html: ${indexFilePresent ? 'Oui' : 'Non'}).`
259
- : "Aucun fichier téléversé.";
 
 
 
 
 
 
 
 
 
 
260
  }
261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  // -------------------------------------------------------------
263
  // --- Logique du Bouton de Déploiement Permanent (Commit) ---
264
  // -------------------------------------------------------------
@@ -267,8 +533,8 @@ deployButton.addEventListener('click', async () => {
267
  // 1. Préparer l'animation et désactiver le bouton
268
  deployButton.disabled = true;
269
  deployButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> Déploiement en cours...';
270
- uploadStatus.className = 'mt-3 text-info fw-bold';
271
- uploadStatus.textContent = 'Téléversement des fichiers sur Hugging Face en cours... (Cela peut prendre quelques instants)';
272
 
273
  // 2. Appel AJAX à la route de commit
274
  try {
@@ -281,50 +547,34 @@ deployButton.addEventListener('click', async () => {
281
  // 3. Traiter la réponse
282
  if (data.success) {
283
  // SUCCÈS
284
- uploadStatus.className = 'mt-3 text-success fw-bold';
285
- uploadStatus.textContent = data.message;
286
-
287
- // Mise à jour des URLs
288
- permanentUrlInput.value = data.url;
289
- permanentUrlContainer.style.display = 'block';
290
- tempUrlContainer.style.display = 'none';
291
 
292
- // Mise à jour du bouton (Succès, non réactivable)
293
- deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_done</span> Déployé !';
294
- deployButton.classList.remove('btn-success');
295
- deployButton.classList.add('btn-secondary');
296
- deployButton.disabled = true;
297
 
298
- launchButton.disabled = false; // Lancer le site déployé
299
-
300
  } else {
301
  // ERREUR
302
- uploadStatus.className = 'mt-3 text-danger fw-bold';
303
- uploadStatus.textContent = `Échec du déploiement: ${data.message}`;
304
-
305
  // Rétablir les boutons à l'état "temporaire" (si l'échec n'est pas critique)
306
- deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)';
307
- deployButton.disabled = !indexFilePresent; // Réactiver si un index.html est présent
308
 
309
- // L'URL temporaire doit rester affichée
310
- if (currentTempUrl && indexFilePresent) {
311
- tempUrlContainer.style.display = 'block';
312
- }
313
  }
314
  } catch (error) {
315
  console.error('Erreur réseau/inattendue:', error);
316
- uploadStatus.className = 'mt-3 text-danger fw-bold';
317
  uploadStatus.textContent = `Erreur réseau ou inattendue lors du déploiement: ${error.message}`;
318
 
319
- // Rétablir les boutons
320
- deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)';
321
- deployButton.disabled = !indexFilePresent;
322
  }
323
  });
324
 
325
 
326
  // -------------------------------------------------------------
327
- // --- Logique de Copie d'URL et Initialisation ---
328
  // -------------------------------------------------------------
329
 
330
  function setupCopyButton(button, input) {
@@ -335,12 +585,12 @@ function setupCopyButton(button, input) {
335
  navigator.clipboard.writeText(input.value)
336
  .then(() => {
337
  button.innerHTML = '<span class="material-symbols-rounded">done</span>';
338
- button.classList.remove('btn-outline-secondary');
339
- button.classList.add('btn-success');
340
  setTimeout(() => {
341
  button.innerHTML = '<span class="material-symbols-rounded">content_copy</span>';
342
- button.classList.remove('btn-success');
343
- button.classList.add('btn-outline-secondary');
344
  }, 2000);
345
  })
346
  .catch(err => {
@@ -364,21 +614,27 @@ launchButton.addEventListener('click', () => {
364
 
365
  // Gère le bouton Vider (doit être implémenté au backend pour la suppression du dossier)
366
  clearButton.addEventListener('click', () => {
367
- // NOTE: La route /clear_deployment n'est pas implémentée, ceci est un rappel pour le développeur.
368
- alert("Fonction Vider: Une route de suppression doit être implémentée au backend pour supprimer le dossier temporaire et vider la session.");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  });
370
 
371
 
372
- // -------------------------------------------------------------
373
- // --- Logique de Glisser-Déposer (Inchagée, doit être là pour fonctionner) ---
374
- // -------------------------------------------------------------
375
-
376
- // NOTE: Les handlers pour Drag/Drop et la sélection de fichiers
377
- // et le téléversement initial (appelant /upload_static_files)
378
- // doivent être implémentés ici ou ailleurs dans votre structure.
379
- // Comme vous n'avez pas fourni ces fonctions, elles sont omises,
380
- // mais updateInitialState se base sur les variables Flask injectées.
381
-
382
  // Initialise l'état au chargement de la page
383
  window.onload = updateInitialState;
384
 
 
107
  @keyframes spinner-border {
108
  to { transform: rotate(360deg); }
109
  }
110
+
111
+ /* -------------------------------------- */
112
+ /* Styles du Mode Sombre */
113
+ /* -------------------------------------- */
114
+
115
+ /* Le corps principal devient sombre */
116
+ body.dark-mode {
117
+ background-color: #1a1a1a;
118
+ color: #f1f1f1;
119
+ }
120
+
121
+ /* Conteneurs et cartes (e.g., action-zone, url-container) */
122
+ .dark-mode .bg-white {
123
+ background-color: #2c2c2c !important;
124
+ color: #f1f1f1;
125
+ }
126
+
127
+ /* Zone de Drop */
128
+ .dark-mode .dropzone {
129
+ border-color: #00bcd4; /* Cyan pour le contraste */
130
+ background-color: #2c2c2c;
131
+ }
132
+ .dark-mode .dropzone.dragover {
133
+ background-color: #404040;
134
+ border-color: #00e5ff;
135
+ }
136
+ .dark-mode .dropzone-icon, .dark-mode .file-icon {
137
+ color: #00bcd4;
138
+ }
139
+ .dark-mode .dropzone-text {
140
+ color: #f1f1f1;
141
+ }
142
+ .dark-mode .dropzone-hint {
143
+ color: #ccc;
144
+ }
145
+
146
+ /* Liste de fichiers */
147
+ .dark-mode .file-list li {
148
+ border-bottom: 1px solid #444;
149
+ }
150
+
151
+ /* Statut de téléversement */
152
+ .dark-mode .text-muted {
153
+ color: #b0b0b0 !important;
154
+ }
155
+
156
+ /* Couleurs des URL (Ajustement pour le fond sombre) */
157
+ .dark-mode #permanent-url-container {
158
+ background-color: #0f5132 !important; /* Vert foncé */
159
+ border-color: #198754 !important;
160
+ }
161
+ .dark-mode .text-success {
162
+ color: #92b8aa !important; /* Vert clair */
163
+ }
164
+
165
+ .dark-mode #temp-url-container {
166
+ background-color: #664d03 !important; /* Jaune foncé */
167
+ border-color: #ffc107 !important;
168
+ }
169
+ .dark-mode .text-warning {
170
+ color: #ffd700 !important; /* Jaune clair */
171
+ }
172
  </style>
173
 
174
+ <div class="d-flex justify-content-end p-3">
175
+ <button id="theme-toggle" class="btn btn-outline-secondary" title="Changer le thème">
176
+ <span id="theme-icon" class="material-symbols-rounded">dark_mode</span>
177
+ </button>
178
+ </div>
179
+
180
  <input type="file" id="file-input" multiple style="display: none;">
181
 
182
 
183
+ <div class="container my-5 mx-auto px-4 lg:px-0">
184
+ <div class="col-12 col-lg-8 mx-auto">
185
+ <h2 class="text-center mb-4 text-3xl font-bold"> Déploiement Statique Instantané</h2>
186
+
187
+ <div id="drop-zone" class="dropzone" data-is-dropping="false">
188
+ <span class="material-symbols-rounded dropzone-icon">cloud_upload</span>
189
+ <p class="dropzone-text">Glissez et déposez vos fichiers ici (HTML, CSS, JS, Images)</p>
190
+ <p class="dropzone-hint">Ou cliquez pour sélectionner des fichiers. Un fichier **index.html** est requis.</p>
191
+ <button id="select-files-button" class="btn bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded mt-3">Sélectionner des Fichiers</button>
192
+ </div>
 
193
 
194
+ <div id="file-upload-status" class="mt-3 text-muted text-center">
195
+ Aucun fichier téléversé.
196
+ </div>
197
 
198
+ <ul id="uploaded-file-list" class="file-list p-3 border rounded shadow-sm bg-white" style="display: none;">
199
+ </ul>
200
+
201
+ <div id="action-zone" class="mt-4 p-4 border rounded shadow-sm bg-white">
202
+ <div class="flex flex-wrap justify-between items-center space-y-2 md:space-y-0">
203
+ <button id="launch-button" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded flex items-center" disabled>
204
+ <span class="material-symbols-rounded align-middle me-1">open_in_new</span> Lancer l'Aperçu (Temp)
205
+ </button>
206
+
207
+ <button id="deploy-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded flex items-center" disabled>
208
+ <span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)
209
+ </button>
210
+
211
+ <button id="clear-button" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded flex items-center" disabled>
212
+ <span class="material-symbols-rounded align-middle me-1">delete_forever</span> Vider
213
+ </button>
 
214
  </div>
215
+ </div>
216
 
217
+ <div id="permanent-url-container" class="mt-4 p-3 bg-success-light border rounded" style="display: none; background-color: #d1e7dd !important; border-color: #badbcc !important;">
218
+ <label class="font-bold d-block text-success" style="color: #0f5132 !important;">
219
+ <span class="material-symbols-rounded align-middle me-1">link</span> Lien Permanent du Site
220
+ </label>
221
+ <div class="flex mt-2">
222
+ <input type="text" id="permanent-url-display" class="w-full p-2 border rounded-l" readonly value="">
223
+ <button class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-3 rounded-r" type="button" id="copy-permanent-button" title="Copier le lien permanent">
224
+ <span class="material-symbols-rounded">content_copy</span>
225
+ </button>
 
 
226
  </div>
227
+ <small class="mt-1 d-block" style="color: #0f5132 !important;"> ✅ Ce lien est sauvegardé sur Hugging Face et est permanent. </small>
228
+ </div>
229
+
230
+ <div id="temp-url-container" class="mt-4 p-3 bg-light border rounded" style="display: none; background-color: #fff3cd !important; border-color: #ffc107 !important;">
231
+ <label class="font-bold d-block text-warning" style="color: #664d03 !important;">
232
+ <span class="material-symbols-rounded align-middle me-1">share</span> Lien d'Aperçu Temporaire
233
+ </label>
234
+ <div class="flex mt-2">
235
+ <input type="text" id="temp-url-display" class="w-full p-2 border rounded-l" readonly value="{{ temp_launch_url if temp_launch_url else '' }}">
236
+ <button class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-3 rounded-r" type="button" id="copy-temp-button" title="Copier le lien temporaire">
237
+ <span class="material-symbols-rounded">content_copy</span>
238
+ </button>
239
  </div>
240
+ <small class="mt-1 d-block"> ⚠️ Ce lien est temporaire. Cliquez sur "**Déployer le Site Web**" pour le rendre permanent. </small>
241
  </div>
242
+
243
  </div>
244
  </div>
245
 
246
  <script>
247
  // Récupération des éléments du DOM
248
+ const body = document.body;
249
+ const themeToggle = document.getElementById('theme-toggle');
250
+ const themeIcon = document.getElementById('theme-icon');
251
+
252
  const dropZone = document.getElementById('drop-zone');
253
  const fileInput = document.getElementById('file-input');
254
  const selectFilesButton = document.getElementById('select-files-button');
 
256
  const uploadedFileList = document.getElementById('uploaded-file-list');
257
  const launchButton = document.getElementById('launch-button');
258
  const clearButton = document.getElementById('clear-button');
259
+ const deployButton = document.getElementById('deploy-button');
260
 
261
  const permanentUrlContainer = document.getElementById('permanent-url-container');
262
  const permanentUrlInput = document.getElementById('permanent-url-display');
 
266
  const tempUrlInput = document.getElementById('temp-url-display');
267
  const copyTempButton = document.getElementById('copy-temp-button');
268
 
269
+ let selectedFiles = []; // Stocke les fichiers à téléverser
270
+
271
  // Données injectées par Flask (Assurez-vous que votre route Flask passe ces variables)
272
+ let currentTempUrl = '{{ temp_launch_url if temp_launch_url else "" }}';
273
+ let currentPermanentUrl = '{{ permanent_deployment_url if permanent_deployment_url else "" }}';
274
+ let initialIndexFilePresent = {{ 'true' if 'index.html' in files else 'false' }};
275
+ let initialFileCount = {{ files|length }};
276
  const deployId = '{{ deploy_id }}';
277
+ const initialFiles = JSON.parse('{{ files | tojson | safe }}');
278
+
279
+ // État initial de l'interface
280
+ let currentIndexFilePresent = initialIndexFilePresent;
281
 
282
  // -------------------------------------------------------------
283
  // --- Fonctions d'Utilitaires et de Mise à Jour de l'Interface ---
284
  // -------------------------------------------------------------
285
 
286
+ /** Affiche la liste des fichiers téléversés ou en attente */
287
  function renderFileList(fileList) {
288
  uploadedFileList.innerHTML = '';
289
  if (fileList.length === 0) {
 
299
  uploadedFileList.style.display = 'block';
300
  }
301
 
302
+ /** Met à jour l'état des boutons et des URL */
303
+ function updateUIState() {
304
+ clearButton.disabled = initialFileCount === 0 && selectedFiles.length === 0;
305
 
306
  if (currentPermanentUrl) {
307
  // État final: URL permanente
308
  permanentUrlInput.value = currentPermanentUrl;
309
  permanentUrlContainer.style.display = 'block';
310
+ tempUrlContainer.style.display = 'none';
311
+
312
  deployButton.disabled = true;
313
  deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_done</span> Déployé !';
314
+ deployButton.classList.remove('bg-green-500', 'hover:bg-green-600');
315
+ deployButton.classList.add('bg-gray-500', 'cursor-not-allowed');
316
  launchButton.disabled = false;
317
+
318
+ uploadStatus.textContent = `Site déployé (${initialFileCount} fichiers).`;
319
+ uploadStatus.className = 'mt-3 text-success fw-bold text-center';
320
+
321
+ } else if (currentTempUrl && currentIndexFilePresent) {
322
  // État temporaire: prêt à être déployé
323
+ tempUrlInput.value = currentTempUrl;
324
  tempUrlContainer.style.display = 'block';
325
+ permanentUrlContainer.style.display = 'none';
326
+
327
  deployButton.disabled = false;
328
+ deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)';
329
+ deployButton.classList.remove('bg-gray-500', 'cursor-not-allowed');
330
+ deployButton.classList.add('bg-green-500', 'hover:bg-green-600');
331
  launchButton.disabled = false;
332
+
333
+ uploadStatus.textContent = `Fichiers téléversés temporairement (${initialFileCount} fichiers. Index.html: Oui). Cliquez sur "Déployer" pour rendre permanent.`;
334
+ uploadStatus.className = 'mt-3 text-info fw-bold text-center';
335
+
336
  } else {
337
  // Aucun fichier ou index.html manquant
 
 
338
  tempUrlContainer.style.display = 'none';
339
  permanentUrlContainer.style.display = 'none';
340
+
341
+ deployButton.disabled = true;
342
+ deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)';
343
+ deployButton.classList.remove('bg-green-500', 'hover:bg-green-600');
344
+ deployButton.classList.add('bg-gray-500', 'cursor-not-allowed');
345
+ launchButton.disabled = true;
346
+
347
+ if (selectedFiles.length > 0) {
348
+ uploadStatus.textContent = `Prêt à téléverser (${selectedFiles.length} fichiers). Index.html: ${currentIndexFilePresent ? 'Oui' : 'Non'}.`;
349
+ uploadStatus.className = 'mt-3 text-muted text-center';
350
+ } else {
351
+ uploadStatus.textContent = "Aucun fichier téléversé.";
352
+ uploadStatus.className = 'mt-3 text-muted text-center';
353
+ }
354
+ }
355
+ }
356
+
357
+ /** Initialise l'état au chargement de la page */
358
+ function updateInitialState() {
359
+ renderFileList(initialFiles);
360
+ updateUIState();
361
+ loadTheme(); // Appliquer le thème sauvegardé
362
+ }
363
+
364
+
365
+ // -------------------------------------------------------------
366
+ // --- Logique du Mode Sombre/Clair ---
367
+ // -------------------------------------------------------------
368
+
369
+ /** Change l'icône et la classe du corps */
370
+ function toggleTheme() {
371
+ const isDarkMode = body.classList.toggle('dark-mode');
372
+ if (isDarkMode) {
373
+ themeIcon.textContent = 'light_mode';
374
+ localStorage.setItem('theme', 'dark');
375
+ } else {
376
+ themeIcon.textContent = 'dark_mode';
377
+ localStorage.setItem('theme', 'light');
378
+ }
379
+ }
380
+
381
+ /** Charge le thème depuis le localStorage */
382
+ function loadTheme() {
383
+ const savedTheme = localStorage.getItem('theme') || 'light';
384
+ if (savedTheme === 'dark') {
385
+ body.classList.add('dark-mode');
386
+ themeIcon.textContent = 'light_mode';
387
+ } else {
388
+ body.classList.remove('dark-mode');
389
+ themeIcon.textContent = 'dark_mode';
390
  }
391
+ }
392
+
393
+ themeToggle.addEventListener('click', toggleTheme);
394
+
395
+
396
+ // -------------------------------------------------------------
397
+ // --- Logique de Glisser-Déposer et Sélection de Fichiers ---
398
+ // -------------------------------------------------------------
399
+
400
+ selectFilesButton.addEventListener('click', () => {
401
+ fileInput.click();
402
+ });
403
+
404
+ fileInput.addEventListener('change', (e) => {
405
+ handleFiles(e.target.files);
406
+ });
407
+
408
+ // Prévention du comportement par défaut
409
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
410
+ dropZone.addEventListener(eventName, preventDefaults, false);
411
+ document.body.addEventListener(eventName, preventDefaults, false);
412
+ });
413
+
414
+ function preventDefaults (e) {
415
+ e.preventDefault();
416
+ e.stopPropagation();
417
+ }
418
+
419
+ // Gestion de l'état 'dragover'
420
+ dropZone.addEventListener('dragenter', highlight, false);
421
+ dropZone.addEventListener('dragover', highlight, false);
422
+ dropZone.addEventListener('dragleave', unhighlight, false);
423
+ dropZone.addEventListener('drop', unhighlight, false);
424
+
425
+ function highlight() {
426
+ dropZone.classList.add('dragover');
427
+ }
428
+
429
+ function unhighlight() {
430
+ dropZone.classList.remove('dragover');
431
+ }
432
+
433
+ dropZone.addEventListener('drop', handleDrop, false);
434
+
435
+ function handleDrop(e) {
436
+ let dt = e.dataTransfer;
437
+ let files = dt.files;
438
+ handleFiles(files);
439
+ }
440
+
441
+ /** Traite la liste des fichiers sélectionnés ou déposés */
442
+ function handleFiles(files) {
443
+ selectedFiles = Array.from(files);
444
+
445
+ // Vérification de index.html
446
+ currentIndexFilePresent = selectedFiles.some(file => file.name.toLowerCase() === 'index.html');
447
 
448
+ // Affichage des fichiers en attente
449
+ const filenames = selectedFiles.map(file => file.name);
450
+ renderFileList(filenames);
451
+
452
+ // Mise à jour de l'état (prêt à téléverser)
453
+ currentTempUrl = '';
454
+ currentPermanentUrl = '';
455
+ initialFileCount = selectedFiles.length; // Pour l'état actuel
456
+ updateUIState();
457
+
458
+ // Lancer le téléversement temporaire immédiatement
459
+ if (selectedFiles.length > 0) {
460
+ uploadFilesToTempEndpoint(selectedFiles);
461
+ }
462
  }
463
 
464
+
465
+ // -------------------------------------------------------------
466
+ // --- Logique du Téléversement TEMPORAIRE vers /upload-static ---
467
+ // -------------------------------------------------------------
468
+
469
+ async function uploadFilesToTempEndpoint(filesToUpload) {
470
+ if (filesToUpload.length === 0) return;
471
+
472
+ // 1. Préparer l'animation et l'état
473
+ uploadStatus.className = 'mt-3 text-warning fw-bold text-center';
474
+ uploadStatus.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> Téléversement des fichiers temporaires...';
475
+
476
+ launchButton.disabled = true;
477
+ deployButton.disabled = true;
478
+
479
+ const formData = new FormData();
480
+ filesToUpload.forEach(file => {
481
+ formData.append('files[]', file); // 'files[]' doit correspondre à la manière dont Flask reçoit les fichiers multiples
482
+ });
483
+
484
+ try {
485
+ const response = await fetch('/upload-static', {
486
+ method: 'POST',
487
+ body: formData
488
+ });
489
+
490
+ const data = await response.json();
491
+
492
+ if (data.status === 'success') {
493
+ // SUCCÈS du téléversement temporaire
494
+ currentTempUrl = data.launch_url;
495
+ currentIndexFilePresent = data.index_present;
496
+ initialFileCount = data.file_count;
497
+
498
+ // L'état initial du déploiement est maintenant basé sur les données du backend
499
+ initialFiles.length = 0; // Vider la liste affichée pour les fichiers fraichement uploadés
500
+ filesToUpload.forEach(file => initialFiles.push(file.name));
501
+ renderFileList(initialFiles);
502
+
503
+ updateUIState(); // Passer à l'état "Temporaire"
504
+ uploadStatus.className = 'mt-3 text-success fw-bold text-center';
505
+ uploadStatus.textContent = data.message;
506
+
507
+ } else {
508
+ // ERREUR
509
+ currentTempUrl = '';
510
+ currentIndexFilePresent = false;
511
+
512
+ updateUIState(); // Revenir à l'état "Aucun fichier" ou "Index manquant"
513
+ uploadStatus.className = 'mt-3 text-danger fw-bold text-center';
514
+ uploadStatus.textContent = `Échec du téléversement: ${data.message}`;
515
+ }
516
+ } catch (error) {
517
+ console.error('Erreur réseau/inattendue lors du téléversement temporaire:', error);
518
+ currentTempUrl = '';
519
+ currentIndexFilePresent = false;
520
+
521
+ updateUIState();
522
+ uploadStatus.className = 'mt-3 text-danger fw-bold text-center';
523
+ uploadStatus.textContent = `Erreur réseau ou inattendue lors du téléversement temporaire: ${error.message}`;
524
+ }
525
+ }
526
+
527
+
528
  // -------------------------------------------------------------
529
  // --- Logique du Bouton de Déploiement Permanent (Commit) ---
530
  // -------------------------------------------------------------
 
533
  // 1. Préparer l'animation et désactiver le bouton
534
  deployButton.disabled = true;
535
  deployButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> Déploiement en cours...';
536
+ uploadStatus.className = 'mt-3 text-info fw-bold text-center';
537
+ uploadStatus.textContent = 'Persistance des fichiers sur Hugging Face et Baserow en cours... (Cela peut prendre quelques instants)';
538
 
539
  // 2. Appel AJAX à la route de commit
540
  try {
 
547
  // 3. Traiter la réponse
548
  if (data.success) {
549
  // SUCCÈS
550
+ currentPermanentUrl = data.url; // Mise à jour de l'URL permanente
551
+ currentTempUrl = '';
 
 
 
 
 
552
 
553
+ updateUIState(); // Passer à l'état "Permanent"
554
+ uploadStatus.className = 'mt-3 text-success fw-bold text-center';
555
+ uploadStatus.textContent = data.message;
 
 
556
 
 
 
557
  } else {
558
  // ERREUR
 
 
 
559
  // Rétablir les boutons à l'état "temporaire" (si l'échec n'est pas critique)
560
+ uploadStatus.className = 'mt-3 text-danger fw-bold text-center';
561
+ uploadStatus.textContent = `Échec du déploiement: ${data.message}`;
562
 
563
+ updateUIState(); // Rétablit les boutons en se basant sur currentTempUrl
564
+
 
 
565
  }
566
  } catch (error) {
567
  console.error('Erreur réseau/inattendue:', error);
568
+ uploadStatus.className = 'mt-3 text-danger fw-bold text-center';
569
  uploadStatus.textContent = `Erreur réseau ou inattendue lors du déploiement: ${error.message}`;
570
 
571
+ updateUIState(); // Rétablit les boutons
 
 
572
  }
573
  });
574
 
575
 
576
  // -------------------------------------------------------------
577
+ // --- Logique de Copie d'URL et Lancement ---
578
  // -------------------------------------------------------------
579
 
580
  function setupCopyButton(button, input) {
 
585
  navigator.clipboard.writeText(input.value)
586
  .then(() => {
587
  button.innerHTML = '<span class="material-symbols-rounded">done</span>';
588
+ button.classList.remove('bg-gray-200');
589
+ button.classList.add('bg-green-500', 'text-white');
590
  setTimeout(() => {
591
  button.innerHTML = '<span class="material-symbols-rounded">content_copy</span>';
592
+ button.classList.remove('bg-green-500', 'text-white');
593
+ button.classList.add('bg-gray-200');
594
  }, 2000);
595
  })
596
  .catch(err => {
 
614
 
615
  // Gère le bouton Vider (doit être implémenté au backend pour la suppression du dossier)
616
  clearButton.addEventListener('click', () => {
617
+ // Si l'utilisateur clique sur Vider, on reset l'état local.
618
+ // NOTE: La suppression du dossier temporaire sur le serveur (route /clear_deployment) doit être implémentée au backend.
619
+
620
+ // Réinitialisation de l'état de l'interface
621
+ selectedFiles = [];
622
+ currentTempUrl = '';
623
+ currentPermanentUrl = '';
624
+ initialFileCount = 0;
625
+ currentIndexFilePresent = false;
626
+ initialFiles.length = 0;
627
+
628
+ renderFileList(initialFiles);
629
+ updateUIState();
630
+
631
+ // Optionnel : Ajouter un appel AJAX à une route de nettoyage backend ici
632
+ // fetch('/clear_deployment', { method: 'POST' });
633
+
634
+ alert("L'état local a été vidé. Pour supprimer le dossier temporaire du serveur, la route '/clear_deployment' doit être implémentée dans Flask.");
635
  });
636
 
637
 
 
 
 
 
 
 
 
 
 
 
638
  // Initialise l'état au chargement de la page
639
  window.onload = updateInitialState;
640