2025-06-12 08:57:10 +00:00
|
|
|
<h1>Download</h1>
|
|
|
|
|
<div class="box">
|
2025-06-21 05:31:26 +00:00
|
|
|
<form hx-post="/api/download/add-multiple" hx-target="#downloads-table" class="box">
|
2025-06-12 08:57:10 +00:00
|
|
|
|
2025-06-21 05:31:26 +00:00
|
|
|
<!-- Zone des liens -->
|
|
|
|
|
<div class="field">
|
|
|
|
|
<label class="label">Liens à débrider</label>
|
|
|
|
|
<div class="control">
|
|
|
|
|
<textarea name="links" class="textarea" rows="5" placeholder="http://...\nhttp://..."></textarea>
|
2025-06-12 08:57:10 +00:00
|
|
|
</div>
|
2025-06-21 05:31:26 +00:00
|
|
|
<p class="help">Un lien par ligne</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Zone de destination -->
|
|
|
|
|
<div class="field">
|
|
|
|
|
<label class="label">Dossier de destination</label>
|
|
|
|
|
<div class="field has-addons">
|
|
|
|
|
<div class="control is-expanded">
|
|
|
|
|
<div class="select is-fullwidth">
|
|
|
|
|
<select name="path_id">
|
|
|
|
|
{{ range .paths }}
|
|
|
|
|
<option value="{{ .ID }}">{{ .PathName }} ({{ .Path }})</option>
|
|
|
|
|
{{ end }}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="control is-expanded">
|
|
|
|
|
<input type="text" name="new_subfolder" class="input" placeholder="Créer un sous-dossier (optionnel)" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Bouton de soumission -->
|
|
|
|
|
<div class="field is-grouped is-justify-content-center">
|
|
|
|
|
<div class="control">
|
|
|
|
|
<button type="submit" class="button is-primary">
|
|
|
|
|
<span class="icon"><i class="fas fa-download"></i></span>
|
|
|
|
|
<span>Télécharger</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-12 08:57:10 +00:00
|
|
|
|
2025-06-20 18:17:24 +00:00
|
|
|
</form>
|
|
|
|
|
|
2025-06-21 05:31:26 +00:00
|
|
|
|
2025-06-20 18:17:24 +00:00
|
|
|
<!-- Tableau des jobs -->
|
|
|
|
|
<div id="downloads-table">
|
|
|
|
|
</div>
|
2025-06-15 15:21:11 +00:00
|
|
|
<div
|
|
|
|
|
hx-ext="sse"
|
|
|
|
|
sse-connect="/api/download/stream"
|
|
|
|
|
hx-on="
|
|
|
|
|
htmx:sseOpen: console.log('✅ SSE ouvert');
|
|
|
|
|
htmx:sseError: console.error('❌ SSE erreur', event.detail.error);
|
|
|
|
|
htmx:sseMessage:console.log('📨 SSE reçu', event.detail);
|
|
|
|
|
"
|
|
|
|
|
>
|
|
|
|
|
<table
|
|
|
|
|
id="downloads-table"
|
|
|
|
|
class="table is-fullwidth is-striped"
|
|
|
|
|
hx-trigger="load,sse:jobs"
|
|
|
|
|
> <thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Fichier</th>
|
|
|
|
|
<th>Statut</th>
|
|
|
|
|
<th>Vitesse</th>
|
|
|
|
|
<th>Progress</th>
|
|
|
|
|
<th>Actions</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="test"></tbody>
|
|
|
|
|
</table>
|
2025-06-12 08:57:10 +00:00
|
|
|
|
|
|
|
|
</div>
|
2025-06-15 15:21:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-19 12:15:07 +00:00
|
|
|
// Fonction d'initialisation de l'EventSource
|
|
|
|
|
function startEventSource() {
|
|
|
|
|
if (es !== null) {
|
|
|
|
|
es.close(); // ferme l'ancien si déjà existant
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
es = new EventSource("/api/download/stream");
|
2025-06-15 15:21:11 +00:00
|
|
|
|
2025-06-19 12:15:07 +00:00
|
|
|
es.addEventListener("jobs", async (e) => {
|
|
|
|
|
console.log("🧪 Event brut reçu :", e.data);
|
2025-06-15 15:21:11 +00:00
|
|
|
|
|
|
|
|
const tbody = document.getElementById("test");
|
|
|
|
|
if (!tbody) {
|
|
|
|
|
console.warn("❌ <tbody id='test'> non trouvé !");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const url = `/api/download/all?t=${Date.now()}`;
|
|
|
|
|
console.log("🔁 Fetch direct :", url);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(url, {
|
2025-06-19 12:15:07 +00:00
|
|
|
headers: { 'HX-Request': 'true' }
|
2025-06-15 15:21:11 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
console.error("❌ Erreur serveur :", response.status);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const html = await response.text();
|
|
|
|
|
tbody.innerHTML = html;
|
|
|
|
|
console.log("✅ Contenu injecté dans <tbody id='test'>");
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("❌ Erreur fetch :", err);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-19 12:15:07 +00:00
|
|
|
es.onerror = e => {
|
|
|
|
|
console.error("❌ Erreur EventSource", e);
|
|
|
|
|
// (optionnel) tu peux tenter de relancer le EventSource ici si besoin
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lancer dès le chargement initial :
|
|
|
|
|
startEventSource();
|
|
|
|
|
|
|
|
|
|
// Fonction POST générique
|
|
|
|
|
async function postJobAction(url) {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(url, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "HX-Request": "true" }
|
|
|
|
|
});
|
|
|
|
|
if (!res.ok) throw new Error("Échec POST : " + res.status);
|
|
|
|
|
console.log("✅ Action POST réussie :", url);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("❌ Erreur action POST :", err);
|
2025-06-15 15:21:11 +00:00
|
|
|
}
|
2025-06-19 12:15:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fonction DELETE avec effet visuel
|
|
|
|
|
async function deleteJobAnimated(jobId) {
|
|
|
|
|
const row = document.getElementById(`job-${jobId}`);
|
|
|
|
|
if (!row) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/download/delete/${jobId}`, {
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
headers: { "HX-Request": "true" }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!res.ok) throw new Error("Échec DELETE");
|
|
|
|
|
|
|
|
|
|
row.style.transition = "opacity 0.4s ease, height 0.4s ease";
|
|
|
|
|
row.style.opacity = "0";
|
|
|
|
|
row.style.height = "0px";
|
|
|
|
|
setTimeout(() => row.remove(), 400);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("❌ Erreur suppression :", err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dispatcher global des boutons
|
|
|
|
|
document.addEventListener("click", function (e) {
|
|
|
|
|
const btn = e.target.closest("button[data-action]");
|
|
|
|
|
if (!btn) return;
|
|
|
|
|
|
|
|
|
|
const action = btn.dataset.action;
|
|
|
|
|
const jobId = btn.dataset.id;
|
|
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
case "start-job":
|
|
|
|
|
postJobAction(`/api/download/start/${jobId}`);
|
|
|
|
|
break;
|
|
|
|
|
case "pause-job":
|
|
|
|
|
postJobAction(`/api/download/pause/${jobId}`);
|
|
|
|
|
break;
|
|
|
|
|
case "resume-job":
|
|
|
|
|
postJobAction(`/api/download/resume/${jobId}`);
|
|
|
|
|
break;
|
|
|
|
|
case "delete-job":
|
|
|
|
|
deleteJobAnimated(jobId);
|
|
|
|
|
break;
|
2025-06-15 15:21:11 +00:00
|
|
|
}
|
2025-06-19 12:15:07 +00:00
|
|
|
});
|
2025-06-15 15:21:11 +00:00
|
|
|
|
|
|
|
|
</script>
|