ernestmindres commited on
Commit
61b8a25
·
verified ·
1 Parent(s): 5306376

Update templates/static_deploy.html

Browse files
Files changed (1) hide show
  1. templates/static_deploy.html +227 -113
templates/static_deploy.html CHANGED
@@ -12,20 +12,21 @@
12
  /* -------------------------------------- */
13
  /* CSS pour la zone de Glisser-Déposer */
14
  /* -------------------------------------- */
 
15
  .dropzone {
16
- border: 3px dashed #007bff; /* Bordure plus visible et couleur primaire */
17
- background-color: #f8f9fa; /* Fond légèrement gris */
18
- padding: 40px; /* Augmenter le padding pour le rendre plus grand */
19
  text-align: center;
20
  cursor: pointer;
21
  transition: background-color 0.2s, border-color 0.2s;
22
- border-radius: 12px; /* Coins arrondis pour un look moderne */
23
  margin-bottom: 20px;
24
- user-select: none; /* Empêcher la sélection de texte */
25
  }
26
 
27
  .dropzone.dragover {
28
- background-color: #e2f0ff; /* Couleur au survol/glissement */
29
  border-color: #0056b3;
30
  }
31
 
@@ -91,6 +92,22 @@
91
  #copy-url-button {
92
  width: 50px; /* Fixer la largeur du bouton de copie */
93
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  </style>
95
 
96
  <input type="file" id="file-input" multiple style="display: none;">
@@ -108,34 +125,55 @@
108
  <div id="upload-status" class="mt-3 text-muted">Prêt pour l'upload.</div>
109
  </div>
110
 
111
- <div id="launch-url-container" class="mt-4 p-3 bg-light border rounded"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  style="display: none; background-color: #fff3cd !important; border-color: #ffc107 !important;">
113
  <label class="form-label fw-bold d-block text-warning" style="color: #664d03 !important;">
114
- <span class="material-symbols-rounded align-middle me-1">share</span> Lien de Déploiement Temporaire (Partageable)
115
  </label>
116
  <div class="input-group mb-2">
117
- <input type="text" id="launch-url-display" class="form-control" readonly
118
- value="{{ launch_url if launch_url else '' }}">
119
- <button class="btn btn-outline-secondary" type="button" id="copy-url-button" title="Copier le lien">
120
  <span class="material-symbols-rounded">content_copy</span>
121
  </button>
122
  </div>
123
  <small class="text-danger mt-1 d-block">
124
- ⚠️ **Attention:** Ce lien est temporaire et sera supprimé lors du prochain redémarrage du service.
125
  </small>
126
  </div>
 
127
  <div class="d-grid gap-2 d-md-flex justify-content-md-center mb-4 mt-4">
 
 
 
128
  <button class="btn btn-primary btn-lg" id="launch-button" disabled>
129
  <span class="material-symbols-rounded align-middle me-1">open_in_new</span> Lancer l'Aperçu
130
  </button>
131
  <button class="btn btn-danger btn-lg" id="clear-button" disabled>
132
- <span class="material-symbols-rounded align-middle me-1">delete</span> Vider
133
  </button>
134
  </div>
135
 
136
  <div class="card shadow-sm">
137
  <div class="card-header bg-light">
138
- <h5 class="mb-0">Fichiers Actuellement Déployés (<span id="file-count">{{ files|length }}</span>)</h5>
139
  </div>
140
  <div class="card-body">
141
  {% if files %}
@@ -156,7 +194,7 @@
156
  </div>
157
 
158
  {% if deploy_id %}
159
- <small class="text-muted mt-3 d-block text-center">ID de Déploiement Temporaire: <code>{{ deploy_id }}</code></small>
160
  {% endif %}
161
 
162
  </div>
@@ -170,28 +208,51 @@ const fileInput = document.getElementById('file-input');
170
  const uploadStatus = document.getElementById('upload-status');
171
  const launchButton = document.getElementById('launch-button');
172
  const clearButton = document.getElementById('clear-button');
173
- const urlContainer = document.getElementById('launch-url-container');
174
- const urlInput = document.getElementById('launch-url-display');
175
- const copyButton = document.getElementById('copy-url-button');
 
 
 
 
 
 
 
 
 
176
  const fileCountSpan = document.getElementById('file-count');
 
 
177
 
178
- // Récupération de l'URL injectée par Flask (sera null si aucun déploiement actif)
179
- const currentLaunchUrl = urlInput.value;
180
 
 
 
 
181
 
182
  /* -------------------------------------- */
183
  /* 0. Initialisation des Boutons/Affichage*/
184
  /* -------------------------------------- */
185
 
186
- // 0a. Afficher l'URL si elle est présente dans le champ (injectée par Jinja2)
187
- if (currentLaunchUrl) {
188
- urlContainer.style.display = 'block';
189
- }
190
-
191
- // 0b. État initial du bouton Lancer (géré par Jinja2 au chargement initial)
192
  const indexFilePresent = {{ index_file_present | tojson }};
193
  const fileCount = {{ files|length }};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
 
195
  if (!indexFilePresent || fileCount === 0) {
196
  launchButton.disabled = true;
197
  } else {
@@ -201,71 +262,46 @@ if (fileCount > 0) {
201
  clearButton.disabled = false;
202
  }
203
 
204
- // 0c. Gère le lancement de l'aperçu (utilise l'URL complète si disponible)
 
205
  launchButton.addEventListener('click', () => {
206
- if (currentLaunchUrl) {
207
- window.open(currentLaunchUrl, '_blank');
208
- } else {
209
- // Logique de secours au cas où l'URL n'a pas été injectée mais le deploy_id est là
210
- const deployId = '{{ deploy_id }}';
211
- if (deployId) {
212
- window.open(`/serve-static/${deployId}/index.html`, '_blank');
213
- }
214
  }
215
  });
216
 
217
 
218
  /* -------------------------------------- */
219
- /* 1. Gestion du Clic sur la Dropzone */
220
  /* -------------------------------------- */
221
- dropZone.addEventListener('click', () => {
222
- fileInput.click();
223
- });
224
 
225
- /* -------------------------------------- */
226
- /* 2. Gestion du Glisser-Déposer */
227
- /* -------------------------------------- */
228
 
229
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
230
  dropZone.addEventListener(eventName, preventDefaults, false);
231
  });
232
 
233
- function preventDefaults(e) {
234
- e.preventDefault();
235
- e.stopPropagation();
236
- }
237
 
238
- ['dragenter', 'dragover'].forEach(eventName => {
239
- dropZone.addEventListener(eventName, highlight, false);
240
- });
241
 
242
- ['dragleave', 'drop'].forEach(eventName => {
243
- dropZone.addEventListener(eventName, unhighlight, false);
244
- });
245
-
246
- function highlight() {
247
- dropZone.classList.add('dragover');
248
- uploadStatus.textContent = "Relâchez les fichiers pour téléverser...";
249
- }
250
-
251
- function unhighlight() {
252
- dropZone.classList.remove('dragover');
253
- }
254
 
255
  dropZone.addEventListener('drop', handleDrop, false);
256
 
257
  function handleDrop(e) {
258
  const dt = e.dataTransfer;
259
- const files = dt.files;
260
-
261
- fileInput.files = files;
262
  handleFileSelection();
263
  }
264
 
265
-
266
- /* -------------------------------------- */
267
- /* 3. Gestion de la Sélection de Fichiers */
268
- /* -------------------------------------- */
269
  fileInput.addEventListener('change', handleFileSelection);
270
 
271
  function handleFileSelection() {
@@ -276,95 +312,173 @@ function handleFileSelection() {
276
 
277
 
278
  /* -------------------------------------- */
279
- /* 4. Logique de l'Upload (AJAX) */
280
  /* -------------------------------------- */
281
  function uploadFiles(files) {
282
  const url = "{{ url_for('web_bp.upload_static_files') }}";
283
  const formData = new FormData();
284
 
285
- // 1. Ajouter tous les fichiers au FormData
286
  for (let i = 0; i < files.length; i++) {
287
  formData.append('file-' + i, files[i]);
288
  }
289
 
290
- // 2. Mettre à jour l'état et désactiver les boutons
291
  uploadStatus.className = 'mt-3 text-info';
292
- uploadStatus.textContent = `Téléversement de ${files.length} fichiers...`;
 
 
293
  launchButton.disabled = true;
294
  clearButton.disabled = true;
 
295
 
296
- // 3. Envoyer la requête AJAX
297
  fetch(url, {
298
  method: 'POST',
299
  body: formData,
300
  })
301
  .then(response => {
302
  if (!response.ok) {
303
- // Tente de lire l'erreur JSON même en cas de statut HTTP 4xx/5xx
304
  return response.json().then(err => { throw new Error(err.message || 'Erreur serveur inconnu'); });
305
  }
306
  return response.json();
307
  })
308
  .then(data => {
309
- // 4. Mettre à jour l'interface avec les résultats
310
  if (data.status === 'success') {
311
  uploadStatus.className = 'mt-3 text-success';
312
- uploadStatus.textContent = data.message + " Redirection en cours...";
313
 
314
- // L'URL de lancement est maintenant dans data.launch_url
315
- // On affiche le lien (temporairement) et on recharge la page pour la robustesse.
316
- urlInput.value = data.launch_url;
317
- urlContainer.style.display = 'block';
318
 
319
- // Recharger pour que le backend (Jinja2) mette à jour l'interface
320
- setTimeout(() => {
 
 
 
 
 
321
  window.location.href = "{{ url_for('web_bp.static_deploy_page') }}";
322
- }, 500); // Petite pause pour que l'utilisateur voie le message de succès
323
-
 
 
 
324
  } else {
325
  uploadStatus.className = 'mt-3 text-danger';
326
  uploadStatus.textContent = `Échec du téléversement: ${data.message}`;
 
 
327
  }
328
  })
329
  .catch(error => {
330
- console.error('Erreur AJAX:', error);
331
  uploadStatus.className = 'mt-3 text-danger';
332
- uploadStatus.textContent = `Erreur critique: ${error.message}`;
333
-
334
- // Réactiver les boutons si l'upload a échoué mais des fichiers étaient présents
335
- if (fileCount > 0) {
336
- launchButton.disabled = !indexFilePresent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  clearButton.disabled = false;
338
  }
 
 
 
 
 
 
 
 
 
 
 
339
  });
340
  }
341
 
342
 
343
  /* -------------------------------------- */
344
- /* 5. Gestion du Bouton de Copie */
345
  /* -------------------------------------- */
346
- copyButton.addEventListener('click', () => {
347
- if (urlInput.value) {
348
- urlInput.select();
349
- urlInput.setSelectionRange(0, 99999); // Pour mobile
350
- navigator.clipboard.writeText(urlInput.value)
351
- .then(() => {
352
- // Rétroaction visuelle
353
- copyButton.innerHTML = '<span class="material-symbols-rounded">done</span>';
354
- copyButton.classList.remove('btn-outline-secondary');
355
- copyButton.classList.add('btn-success');
356
- setTimeout(() => {
357
- copyButton.innerHTML = '<span class="material-symbols-rounded">content_copy</span>';
358
- copyButton.classList.remove('btn-success');
359
- copyButton.classList.add('btn-outline-secondary');
360
- }, 2000);
361
- })
362
- .catch(err => {
363
- console.error('Erreur lors de la copie: ', err);
364
- alert('Erreur lors de la copie du lien.');
365
- });
366
- }
367
- });
 
 
 
 
 
368
 
369
 
370
  // Gère le bouton Vider (doit être implémenté au backend pour la suppression du dossier)
 
12
  /* -------------------------------------- */
13
  /* CSS pour la zone de Glisser-Déposer */
14
  /* -------------------------------------- */
15
+ /* ... (styles de dropzone inchangés) ... */
16
  .dropzone {
17
+ border: 3px dashed #007bff;
18
+ background-color: #f8f9fa;
19
+ padding: 40px;
20
  text-align: center;
21
  cursor: pointer;
22
  transition: background-color 0.2s, border-color 0.2s;
23
+ border-radius: 12px;
24
  margin-bottom: 20px;
25
+ user-select: none;
26
  }
27
 
28
  .dropzone.dragover {
29
+ background-color: #e2f0ff;
30
  border-color: #0056b3;
31
  }
32
 
 
92
  #copy-url-button {
93
  width: 50px; /* Fixer la largeur du bouton de copie */
94
  }
95
+
96
+ /* Nouveau style pour l'animation de chargement */
97
+ .spinner-border {
98
+ display: inline-block;
99
+ width: 1rem;
100
+ height: 1rem;
101
+ vertical-align: middle;
102
+ border: .2em solid currentColor;
103
+ border-right-color: transparent;
104
+ border-radius: 50%;
105
+ animation: spinner-border .75s linear infinite;
106
+ }
107
+
108
+ @keyframes spinner-border {
109
+ to { transform: rotate(360deg); }
110
+ }
111
  </style>
112
 
113
  <input type="file" id="file-input" multiple style="display: none;">
 
125
  <div id="upload-status" class="mt-3 text-muted">Prêt pour l'upload.</div>
126
  </div>
127
 
128
+ <div id="permanent-url-container" class="mt-4 p-3 border rounded"
129
+ style="display: none; background-color: #d1e7dd !important; border-color: #badbcc !important;">
130
+ <label class="form-label fw-bold d-block text-success" style="color: #0f5132 !important;">
131
+ <span class="material-symbols-rounded align-middle me-1">check_circle</span> Lien de Déploiement Permanent
132
+ </label>
133
+ <div class="input-group mb-2">
134
+ <input type="text" id="permanent-url-display" class="form-control" readonly
135
+ value="{{ permanent_deployment_url if permanent_deployment_url else '' }}">
136
+ <button class="btn btn-outline-secondary" type="button" id="copy-permanent-button" title="Copier le lien permanent">
137
+ <span class="material-symbols-rounded">content_copy</span>
138
+ </button>
139
+ </div>
140
+ <small class="text-muted mt-1 d-block">
141
+ ✅ Ce lien est sauvegardé sur Hugging Face et est permanent.
142
+ </small>
143
+ </div>
144
+
145
+ <div id="temp-url-container" class="mt-4 p-3 bg-light border rounded"
146
  style="display: none; background-color: #fff3cd !important; border-color: #ffc107 !important;">
147
  <label class="form-label fw-bold d-block text-warning" style="color: #664d03 !important;">
148
+ <span class="material-symbols-rounded align-middle me-1">share</span> Lien d'Aperçu Temporaire
149
  </label>
150
  <div class="input-group mb-2">
151
+ <input type="text" id="temp-url-display" class="form-control" readonly
152
+ value="{{ temp_launch_url if temp_launch_url else '' }}">
153
+ <button class="btn btn-outline-secondary" type="button" id="copy-temp-button" title="Copier le lien temporaire">
154
  <span class="material-symbols-rounded">content_copy</span>
155
  </button>
156
  </div>
157
  <small class="text-danger mt-1 d-block">
158
+ ⚠️ **Attention:** Ce lien est temporaire. Cliquez sur "Déployer le Site Web" pour le rendre permanent.
159
  </small>
160
  </div>
161
+
162
  <div class="d-grid gap-2 d-md-flex justify-content-md-center mb-4 mt-4">
163
+ <button class="btn btn-success btn-lg" id="deploy-button" disabled>
164
+ <span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)
165
+ </button>
166
  <button class="btn btn-primary btn-lg" id="launch-button" disabled>
167
  <span class="material-symbols-rounded align-middle me-1">open_in_new</span> Lancer l'Aperçu
168
  </button>
169
  <button class="btn btn-danger btn-lg" id="clear-button" disabled>
170
+ <span class="material-symbols-rounded align-middle me-1">delete</span> Vider le Cache
171
  </button>
172
  </div>
173
 
174
  <div class="card shadow-sm">
175
  <div class="card-header bg-light">
176
+ <h5 class="mb-0">Fichiers Actuellement Téléversés (<span id="file-count">{{ files|length }}</span>)</h5>
177
  </div>
178
  <div class="card-body">
179
  {% if files %}
 
194
  </div>
195
 
196
  {% if deploy_id %}
197
+ <small class="text-muted mt-3 d-block text-center">ID de Projet (Cache Local): <code>{{ deploy_id }}</code></small>
198
  {% endif %}
199
 
200
  </div>
 
208
  const uploadStatus = document.getElementById('upload-status');
209
  const launchButton = document.getElementById('launch-button');
210
  const clearButton = document.getElementById('clear-button');
211
+
212
+ // NOUVEAU
213
+ const deployButton = document.getElementById('deploy-button');
214
+ const permanentUrlContainer = document.getElementById('permanent-url-container');
215
+ const permanentUrlInput = document.getElementById('permanent-url-display');
216
+ const copyPermanentButton = document.getElementById('copy-permanent-button');
217
+
218
+ // EXISTANT MAIS MODIFIÉ
219
+ const tempUrlContainer = document.getElementById('temp-url-container');
220
+ const tempUrlInput = document.getElementById('temp-url-display');
221
+ const copyTempButton = document.getElementById('copy-temp-button');
222
+
223
  const fileCountSpan = document.getElementById('file-count');
224
+ const uploadedFileList = document.getElementById('uploaded-file-list');
225
+ const noFilesMessage = document.getElementById('no-files-message');
226
 
 
 
227
 
228
+ // Récupération de l'URL injectée par Flask
229
+ const currentTempUrl = tempUrlInput.value;
230
+ const currentPermanentUrl = permanentUrlInput.value;
231
 
232
  /* -------------------------------------- */
233
  /* 0. Initialisation des Boutons/Affichage*/
234
  /* -------------------------------------- */
235
 
 
 
 
 
 
 
236
  const indexFilePresent = {{ index_file_present | tojson }};
237
  const fileCount = {{ files|length }};
238
+ const deployId = '{{ deploy_id }}';
239
+
240
+
241
+ // 0a. Gestion de l'affichage de l'URL
242
+ if (currentPermanentUrl) {
243
+ // Si l'URL permanente est là, on affiche le lien permanent et on désactive le bouton de déploiement.
244
+ permanentUrlContainer.style.display = 'block';
245
+ deployButton.disabled = true;
246
+ tempUrlContainer.style.display = 'none';
247
+ } else if (currentTempUrl) {
248
+ // Si l'URL temporaire est là (avant commit)
249
+ tempUrlContainer.style.display = 'block';
250
+ deployButton.disabled = false;
251
+ permanentUrlContainer.style.display = 'none';
252
+ }
253
+
254
 
255
+ // 0b. État initial des boutons Lancer et Vider
256
  if (!indexFilePresent || fileCount === 0) {
257
  launchButton.disabled = true;
258
  } else {
 
262
  clearButton.disabled = false;
263
  }
264
 
265
+
266
+ // 0c. Gère le lancement de l'aperçu
267
  launchButton.addEventListener('click', () => {
268
+ // Utiliser l'URL permanente si disponible, sinon la temporaire
269
+ const urlToLaunch = currentPermanentUrl || currentTempUrl;
270
+ if (urlToLaunch) {
271
+ window.open(urlToLaunch, '_blank');
272
+ } else if (deployId && indexFilePresent) {
273
+ window.open(`/serve-static/${deployId}/index.html`, '_blank');
 
 
274
  }
275
  });
276
 
277
 
278
  /* -------------------------------------- */
279
+ /* 1, 2, 3 : Gestion de l'Upload (UI) */
280
  /* -------------------------------------- */
 
 
 
281
 
282
+ // ... (Gestion de la Dropzone et du File Input inchangée) ...
283
+ dropZone.addEventListener('click', () => { fileInput.click(); });
 
284
 
285
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
286
  dropZone.addEventListener(eventName, preventDefaults, false);
287
  });
288
 
289
+ function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); }
 
 
 
290
 
291
+ ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, highlight, false); });
292
+ ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, unhighlight, false); });
 
293
 
294
+ function highlight() { dropZone.classList.add('dragover'); uploadStatus.textContent = "Relâchez les fichiers pour téléverser..."; }
295
+ function unhighlight() { dropZone.classList.remove('dragover'); }
 
 
 
 
 
 
 
 
 
 
296
 
297
  dropZone.addEventListener('drop', handleDrop, false);
298
 
299
  function handleDrop(e) {
300
  const dt = e.dataTransfer;
301
+ fileInput.files = dt.files;
 
 
302
  handleFileSelection();
303
  }
304
 
 
 
 
 
305
  fileInput.addEventListener('change', handleFileSelection);
306
 
307
  function handleFileSelection() {
 
312
 
313
 
314
  /* -------------------------------------- */
315
+ /* 4. Logique de l'Upload (AJAX TEMPORAIRE)*/
316
  /* -------------------------------------- */
317
  function uploadFiles(files) {
318
  const url = "{{ url_for('web_bp.upload_static_files') }}";
319
  const formData = new FormData();
320
 
 
321
  for (let i = 0; i < files.length; i++) {
322
  formData.append('file-' + i, files[i]);
323
  }
324
 
 
325
  uploadStatus.className = 'mt-3 text-info';
326
+ uploadStatus.textContent = `Téléversement de ${files.length} fichiers en cours...`;
327
+
328
+ // Désactiver tous les boutons pendant l'upload
329
  launchButton.disabled = true;
330
  clearButton.disabled = true;
331
+ deployButton.disabled = true;
332
 
 
333
  fetch(url, {
334
  method: 'POST',
335
  body: formData,
336
  })
337
  .then(response => {
338
  if (!response.ok) {
 
339
  return response.json().then(err => { throw new Error(err.message || 'Erreur serveur inconnu'); });
340
  }
341
  return response.json();
342
  })
343
  .then(data => {
 
344
  if (data.status === 'success') {
345
  uploadStatus.className = 'mt-3 text-success';
346
+ uploadStatus.textContent = data.message;
347
 
348
+ // Mise à jour de l'URL temporaire et affichage du conteneur temporaire
349
+ tempUrlInput.value = data.launch_url;
350
+ tempUrlContainer.style.display = 'block';
351
+ permanentUrlContainer.style.display = 'none'; // Cacher l'ancien permanent
352
 
353
+ // Réactiver les boutons selon l'état
354
+ launchButton.disabled = !data.index_present;
355
+ clearButton.disabled = false;
356
+ deployButton.disabled = !data.index_present; // On ne peut déployer que s'il y a un index.html
357
+
358
+ // Mise à jour de la liste des fichiers (rechargement pour la simplicité)
359
+ setTimeout(() => {
360
  window.location.href = "{{ url_for('web_bp.static_deploy_page') }}";
361
+ }, 500);
362
+
363
+ // Si la liste était mise à jour côté client (sans rechargement):
364
+ // updateFileList(data.uploaded_files_list, data.index_present, data.file_count);
365
+
366
  } else {
367
  uploadStatus.className = 'mt-3 text-danger';
368
  uploadStatus.textContent = `Échec du téléversement: ${data.message}`;
369
+ // Réactiver les boutons de base si l'upload a échoué
370
+ clearButton.disabled = fileCount === 0;
371
  }
372
  })
373
  .catch(error => {
374
+ console.error('Erreur AJAX Upload:', error);
375
  uploadStatus.className = 'mt-3 text-danger';
376
+ uploadStatus.textContent = `Erreur critique lors de l'upload: ${error.message}`;
377
+ clearButton.disabled = false;
378
+ });
379
+ }
380
+
381
+
382
+ /* -------------------------------------- */
383
+ /* 5. Logique du Déploiement (AJAX PERMANENT) */
384
+ /* -------------------------------------- */
385
+ deployButton.addEventListener('click', commitDeployment);
386
+
387
+ function commitDeployment() {
388
+ const url = "{{ url_for('web_bp.deploy_to_hf') }}";
389
+
390
+ // 1. Démarrer l'animation de chargement
391
+ deployButton.disabled = true;
392
+ launchButton.disabled = true;
393
+ clearButton.disabled = true;
394
+ deployButton.innerHTML = '<span class="spinner-border text-light me-2"></span> Déploiement en cours...';
395
+
396
+ uploadStatus.className = 'mt-3 text-info';
397
+ uploadStatus.textContent = `Déploiement permanent sur Hugging Face en cours... (Cela peut prendre quelques instants)`;
398
+
399
+ // 2. Envoyer la requête AJAX
400
+ fetch(url, {
401
+ method: 'POST',
402
+ })
403
+ .then(response => {
404
+ // Tente de lire l'erreur JSON pour un statut HTTP 4xx/5xx
405
+ if (!response.ok) {
406
+ return response.json().then(err => { throw new Error(err.message || 'Erreur serveur inconnu'); });
407
+ }
408
+ return response.json();
409
+ })
410
+ .then(data => {
411
+ // 3. Mettre à jour l'interface en cas de succès
412
+ if (data.success) {
413
+ uploadStatus.className = 'mt-3 text-success';
414
+ uploadStatus.textContent = data.message + " Redirection en cours...";
415
+
416
+ // Mise à jour de l'URL permanente et de l'affichage
417
+ permanentUrlInput.value = data.url;
418
+ permanentUrlContainer.style.display = 'block';
419
+ tempUrlContainer.style.display = 'none';
420
+
421
+ // Recharger pour que Flask mette à jour l'interface JINJA2 pour un état permanent
422
+ setTimeout(() => {
423
+ window.location.href = "{{ url_for('web_bp.static_deploy_page') }}";
424
+ }, 500);
425
+
426
+ } else {
427
+ // 4. Mettre à jour l'interface en cas d'échec
428
+ uploadStatus.className = 'mt-3 text-danger';
429
+ uploadStatus.textContent = `Échec du déploiement: ${data.message}`;
430
+
431
+ // Rétablir les boutons à l'état "temporaire"
432
+ deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)';
433
+ deployButton.disabled = false;
434
+ launchButton.disabled = !indexFilePresent;
435
  clearButton.disabled = false;
436
  }
437
+ })
438
+ .catch(error => {
439
+ console.error('Erreur AJAX Déploiement:', error);
440
+ uploadStatus.className = 'mt-3 text-danger';
441
+ uploadStatus.textContent = `Erreur critique lors du déploiement: ${error.message}`;
442
+
443
+ // Rétablir les boutons à l'état "temporaire"
444
+ deployButton.innerHTML = '<span class="material-symbols-rounded align-middle me-1">cloud_upload</span> Déployer le Site Web (Permanent)';
445
+ deployButton.disabled = false;
446
+ launchButton.disabled = !indexFilePresent;
447
+ clearButton.disabled = false;
448
  });
449
  }
450
 
451
 
452
  /* -------------------------------------- */
453
+ /* 6. Gestion des Boutons de Copie */
454
  /* -------------------------------------- */
455
+
456
+ function setupCopyButton(button, input) {
457
+ button.addEventListener('click', () => {
458
+ if (input.value) {
459
+ input.select();
460
+ input.setSelectionRange(0, 99999);
461
+ navigator.clipboard.writeText(input.value)
462
+ .then(() => {
463
+ button.innerHTML = '<span class="material-symbols-rounded">done</span>';
464
+ button.classList.remove('btn-outline-secondary');
465
+ button.classList.add('btn-success');
466
+ setTimeout(() => {
467
+ button.innerHTML = '<span class="material-symbols-rounded">content_copy</span>';
468
+ button.classList.remove('btn-success');
469
+ button.classList.add('btn-outline-secondary');
470
+ }, 2000);
471
+ })
472
+ .catch(err => {
473
+ console.error('Erreur lors de la copie: ', err);
474
+ alert('Erreur lors de la copie du lien.');
475
+ });
476
+ }
477
+ });
478
+ }
479
+
480
+ setupCopyButton(copyTempButton, tempUrlInput);
481
+ setupCopyButton(copyPermanentButton, permanentUrlInput);
482
 
483
 
484
  // Gère le bouton Vider (doit être implémenté au backend pour la suppression du dossier)