diff --git a/public/js/download-manager.js b/public/js/download-manager.js index 4ff6e2e..0dec026 100644 --- a/public/js/download-manager.js +++ b/public/js/download-manager.js @@ -1,28 +1,64 @@ -$(() => { - $('input.uk-checkbox').each((i, e) => { - $(e).change(() => { - loadConfig(); - }); - }); - loadConfig(); - load(); - - const intervalMS = 5000; - setTimeout(() => { - setInterval(() => { - if (globalConfig.autoRefresh !== true) return; - load(); - }, intervalMS); - }, intervalMS); -}); -var globalConfig = {}; -var loading = false; - -const loadConfig = () => { - globalConfig.autoRefresh = $('#auto-refresh').prop('checked'); +/** + * Set an alpine.js property + * + * @function setProp + * @param {string} key - Key of the data property + * @param {*} prop - The data property + */ +const setProp = (key, prop) => { + $('#root').get(0).__x.$data[key] = prop; }; -const remove = (id) => { - var url = base_url + 'api/admin/mangadex/queue/delete'; + +/** + * Get an alpine.js property + * + * @function getProp + * @param {string} key - Key of the data property + * @return {*} The data property + */ +const getProp = (key) => { + return $('#root').get(0).__x.$data[key]; +}; + +/** + * Get the current queue and update the view + * + * @function load + */ +const load = () => { + try { + setProp('loading', true); + } catch {} + $.ajax({ + type: 'GET', + url: base_url + 'api/admin/mangadex/queue', + dataType: 'json' + }) + .done(data => { + if (!data.success && data.error) { + alert('danger', `Failed to fetch download queue. Error: ${data.error}`); + return; + } + setProp('jobs', data.jobs); + setProp('paused', data.paused); + }) + .fail((jqXHR, status) => { + alert('danger', `Failed to fetch download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`); + }) + .always(() => { + setProp('loading', false); + }); +}; + +/** + * Perform an action on either a specific job or the entire queue + * + * @function jobAction + * @param {string} action - The action to perform. Should be either 'delete' or 'retry' + * @param {string?} id - (Optional) A job ID. When omitted, apply the action to the queue + */ +const jobAction = (action, id) => { + let url = `${base_url}api/admin/mangadex/queue/${action}`; if (id !== undefined) url += '?' + $.param({ id: id @@ -35,42 +71,24 @@ const remove = (id) => { }) .done(data => { if (!data.success && data.error) { - alert('danger', `Failed to remove job from download queue. Error: ${data.error}`); + alert('danger', `Failed to ${action} job from download queue. Error: ${data.error}`); return; } load(); }) .fail((jqXHR, status) => { - alert('danger', `Failed to remove job from download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`); - }); -}; -const refresh = (id) => { - var url = base_url + 'api/admin/mangadex/queue/retry'; - if (id !== undefined) - url += '?' + $.param({ - id: id - }); - console.log(url); - $.ajax({ - type: 'POST', - url: url, - dataType: 'json' - }) - .done(data => { - if (!data.success && data.error) { - alert('danger', `Failed to restart download job. Error: ${data.error}`); - return; - } - load(); - }) - .fail((jqXHR, status) => { - alert('danger', `Failed to restart download job. Error: [${jqXHR.status}] ${jqXHR.statusText}`); + alert('danger', `Failed to ${action} job from download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`); }); }; + +/** + * Pause/resume the download + * + * @function toggle + */ const toggle = () => { - $('#pause-resume-btn').attr('disabled', ''); - const paused = $('#pause-resume-btn').text() === 'Resume download'; - const action = paused ? 'resume' : 'pause'; + setProp('toggling', true); + const action = getProp('paused') ? 'resume' : 'pause'; const url = `${base_url}api/admin/mangadex/queue/${action}`; $.ajax({ type: 'POST', @@ -82,64 +100,47 @@ const toggle = () => { }) .always(() => { load(); - $('#pause-resume-btn').removeAttr('disabled'); + setProp('toggling', false); }); }; -const load = () => { - if (loading) return; - loading = true; - console.log('fetching'); - $.ajax({ - type: 'GET', - url: base_url + 'api/admin/mangadex/queue', - dataType: 'json' - }) - .done(data => { - if (!data.success && data.error) { - alert('danger', `Failed to fetch download queue. Error: ${data.error}`); - return; - } - console.log(data); - const btnText = data.paused ? "Resume download" : "Pause download"; - $('#pause-resume-btn').text(btnText); - $('#pause-resume-btn').removeAttr('hidden'); - const rows = data.jobs.map(obj => { - var cls = 'label '; - if (obj.status === 'Pending') - cls += 'label-pending'; - if (obj.status === 'Completed') - cls += 'label-success'; - if (obj.status === 'Error') - cls += 'label-danger'; - if (obj.status === 'MissingPages') - cls += 'label-warning'; - const info = obj.status_message.length > 0 ? '' : ''; - const statusSpan = `${obj.status} ${info}`; - const dropdown = obj.status_message.length > 0 ? `
${obj.status_message}
` : ''; - const retryBtn = obj.status_message.length > 0 ? `` : ''; - return ` - ${obj.plugin_id ? obj.title : `${obj.title}`} - ${obj.plugin_id ? obj.manga_title : `${obj.manga_title}`} - ${obj.success_count}/${obj.pages} - ${moment(obj.time).fromNow()} - ${statusSpan} ${dropdown} - ${obj.plugin_id || ""} - - - ${retryBtn} - - `; - }); - - const tbody = `${rows.join('')}`; - $('tbody').remove(); - $('table').append(tbody); - }) - .fail((jqXHR, status) => { - alert('danger', `Failed to fetch download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`); - }) - .always(() => { - loading = false; - }); +/** + * Get the uk-label class name for a given job status + * + * @function statusClass + * @param {string} status - The job status + * @return {string} The class name string + */ +const statusClass = status => { + let cls = 'label '; + switch (status) { + case 'Pending': + cls += 'label-pending'; + break; + case 'Completed': + cls += 'label-success'; + break; + case 'Error': + cls += 'label-danger'; + break; + case 'MissingPages': + cls += 'label-warning'; + break; + } + return cls; }; + +$(() => { + const ws = new WebSocket(`ws://${location.host}/api/admin/mangadex/queue`); + ws.onmessage = event => { + const data = JSON.parse(event.data); + setProp('jobs', data.jobs); + setProp('paused', data.paused); + }; + ws.onerror = err => { + alert('danger', `Socket connection failed. Error: ${err}`); + }; + ws.onclose = err => { + alert('danger', 'Socket connection failed'); + }; +}); diff --git a/src/routes/api.cr b/src/routes/api.cr index ce2caf5..11a6134 100644 --- a/src/routes/api.cr +++ b/src/routes/api.cr @@ -212,6 +212,18 @@ class APIRouter < Router end end + ws "/api/admin/mangadex/queue" do |socket, env| + interval_raw = env.params.query["interval"]? + interval = (interval_raw.to_i? if interval_raw) || 5 + loop do + socket.send({ + "jobs" => @context.queue.get_all, + "paused" => @context.queue.paused?, + }.to_json) + sleep interval.seconds + end + end + get "/api/admin/mangadex/queue" do |env| begin jobs = @context.queue.get_all diff --git a/src/views/download-manager.html.ecr b/src/views/download-manager.html.ecr index ec8618a..1cd90ec 100644 --- a/src/views/download-manager.html.ecr +++ b/src/views/download-manager.html.ecr @@ -1,32 +1,68 @@ -
-
- - - - -
-
- +
+
+ + + +
+ + + + + + + + + + + + + + + +
ChapterMangaProgressTimeStatusPluginActions
- - - - - - - - - - - - -
ChapterMangaProgressTimeStatusPluginActions
<% content_for "script" do %> -