From: Marcin Haba Date: Sun, 1 Mar 2020 16:13:15 +0000 (+0100) Subject: baculum: Add bulk actions for job history and volume tables X-Git-Tag: Release-9.6.3~17 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=63c0e0a18357b9f8591bd27397f928f68c12478f;p=thirdparty%2Fbacula.git baculum: Add bulk actions for job history and volume tables - add 'cancel' and 'delete' actions to job history table - add 'prune' and 'purge' action to volume table --- diff --git a/gui/baculum/protected/Common/Class/BClientScript.php b/gui/baculum/protected/Common/Class/BClientScript.php index 33b56e8fe..baf6878b5 100644 --- a/gui/baculum/protected/Common/Class/BClientScript.php +++ b/gui/baculum/protected/Common/Class/BClientScript.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -31,7 +31,7 @@ Prado::using('System.Web.UI.WebControls.TClientScript'); */ class BClientScript extends TClientScript { - const SCRIPTS_VERSION = 5; + const SCRIPTS_VERSION = 6; public function getScriptUrl() { diff --git a/gui/baculum/protected/Web/JavaScript/misc.js b/gui/baculum/protected/Web/JavaScript/misc.js index 020dff3ce..5c5342efe 100644 --- a/gui/baculum/protected/Web/JavaScript/misc.js +++ b/gui/baculum/protected/Web/JavaScript/misc.js @@ -1052,6 +1052,9 @@ function sort_natural(a, b) { } function update_job_table(table_obj, new_data) { + + var current_page = table_obj.page(); + var rows = table_obj.rows(); var old_jobs = {}; table_obj.data().toArray().forEach(function(job) { @@ -1104,6 +1107,8 @@ function update_job_table(table_obj, new_data) { for (var jobid in job_add_mod) { table_obj.row.add(job_add_mod[jobid]).draw(); } + + table_obj.page(current_page).draw(false); } /** @@ -1117,6 +1122,64 @@ dtEscapeRegex = function(value) { return $.fn.dataTable.util.escapeRegex(value); }; + +function get_table_toolbar(table, actions, txt) { + var table_toolbar = document.querySelector('div.table_toolbar'); + table_toolbar.className += ' dt-buttons'; + table_toolbar.style.display = 'none'; + var title = document.createTextNode(txt.actions); + var select = document.createElement('SELECT'); + var option = document.createElement('OPTION'); + option.value = ''; + select.appendChild(option); + var acts = {}; + for (var i = 0; i < actions.length; i++) { + option = document.createElement('OPTION'); + option.value = actions[i].action, + label = document.createTextNode(actions[i].label); + option.appendChild(label); + select.appendChild(option); + acts[actions[i].action] = actions[i]; + } + var btn = document.createElement('BUTTON'); + btn.type = 'button'; + btn.className = 'dt-button'; + btn.style.verticalAlign = 'top'; + label = document.createTextNode(txt.ok); + btn.appendChild(label); + btn.addEventListener('click', function(e) { + if (!select.value) { + // no value, no action + return + } + var selected = []; + var sel_data = table.rows({selected: true}).data(); + sel_data.each(function(v, k) { + selected.push(v[acts[select.value].value]); + }); + + // call validation if defined + if (acts[select.value].hasOwnProperty('validate') && typeof(acts[select.value].validate) == 'function') { + if (acts[select.value].validate(sel_data) === false) { + // validation error + return false; + } + } + // call pre-action before calling bulk action + if (acts[select.value].hasOwnProperty('before') && typeof(acts[select.value].before) == 'function') { + acts[select.value].before(); + } + selected = selected.join('|'); + acts[select.value].callback.options.RequestTimeOut = 60000; // Timeout set to 1 minute + acts[select.value].callback.setCallbackParameter(selected); + acts[select.value].callback.dispatch(); + }); + table_toolbar.appendChild(title); + table_toolbar.appendChild(select); + table_toolbar.appendChild(btn); + return table_toolbar; +} + $(function() { W3SideBar.init(); set_sbbr_compatibility(); diff --git a/gui/baculum/protected/Web/Lang/en/messages.mo b/gui/baculum/protected/Web/Lang/en/messages.mo index f07a72705..ba3772519 100644 Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/en/messages.po b/gui/baculum/protected/Web/Lang/en/messages.po index 154e18560..cbca887ec 100644 --- a/gui/baculum/protected/Web/Lang/en/messages.po +++ b/gui/baculum/protected/Web/Lang/en/messages.po @@ -2614,3 +2614,12 @@ msgstr "Last part bytes" msgid "Cache retention" msgstr "Cache retention" + +msgid "Bulk action" +msgstr "Bulk action" + +msgid "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again." +msgstr "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again." + +msgid "Refresh page" +msgstr "Refresh page" diff --git a/gui/baculum/protected/Web/Lang/ja/messages.mo b/gui/baculum/protected/Web/Lang/ja/messages.mo index a9ee1e634..b52cef41e 100644 Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/ja/messages.po b/gui/baculum/protected/Web/Lang/ja/messages.po index 35e385f33..f80bfed39 100644 --- a/gui/baculum/protected/Web/Lang/ja/messages.po +++ b/gui/baculum/protected/Web/Lang/ja/messages.po @@ -2700,3 +2700,12 @@ msgstr "Last part bytes" msgid "Cache retention" msgstr "Cache retention" + +msgid "Bulk action" +msgstr "Bulk action" + +msgid "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again." +msgstr "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again." + +msgid "Refresh page" +msgstr "Refresh page" diff --git a/gui/baculum/protected/Web/Lang/pl/messages.mo b/gui/baculum/protected/Web/Lang/pl/messages.mo index ae0c57db3..b70a33bfa 100644 Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pl/messages.po b/gui/baculum/protected/Web/Lang/pl/messages.po index ede8d7a86..582b6202b 100644 --- a/gui/baculum/protected/Web/Lang/pl/messages.po +++ b/gui/baculum/protected/Web/Lang/pl/messages.po @@ -2623,3 +2623,11 @@ msgstr "Ostatnia część w bajtach" msgid "Cache retention" msgstr "Retencja pamięci podręcznej" +msgid "Bulk action" +msgstr "Akcja zbiorowa" + +msgid "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again." +msgstr "Następujące zadania są uruchomione i nie mogą zostać usunięte: %running_jobs Aby je usunąć, proszę je zatrzymać i spróbować ponownie." + +msgid "Refresh page" +msgstr "Odśwież stronę" diff --git a/gui/baculum/protected/Web/Lang/pt/messages.mo b/gui/baculum/protected/Web/Lang/pt/messages.mo index 564e2ac9e..2c925d3c8 100644 Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pt/messages.po b/gui/baculum/protected/Web/Lang/pt/messages.po index 3436ef189..4881fa776 100644 --- a/gui/baculum/protected/Web/Lang/pt/messages.po +++ b/gui/baculum/protected/Web/Lang/pt/messages.po @@ -2623,3 +2623,11 @@ msgstr "Bytes da última parte" msgid "Cache retention" msgstr "Retenção de cache" +msgid "Bulk action" +msgstr "Bulk action" + +msgid "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again." +msgstr "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again." + +msgid "Refresh page" +msgstr "Refresh page" diff --git a/gui/baculum/protected/Web/Layouts/Main.tpl b/gui/baculum/protected/Web/Layouts/Main.tpl index 2abc57f6f..721159980 100644 --- a/gui/baculum/protected/Web/Layouts/Main.tpl +++ b/gui/baculum/protected/Web/Layouts/Main.tpl @@ -13,14 +13,15 @@ /> /> /> + /> + /> + /> + /> /> /> /> /> /> - /> - /> - />
diff --git a/gui/baculum/protected/Web/Pages/JobHistoryList.page b/gui/baculum/protected/Web/Pages/JobHistoryList.page index 01f3b0907..267c4dcf5 100644 --- a/gui/baculum/protected/Web/Pages/JobHistoryList.page +++ b/gui/baculum/protected/Web/Pages/JobHistoryList.page @@ -102,6 +102,43 @@ function refresh_job_history(jobid) { document.location.href = '<%=$this->Service->constructUrl('JobHistoryList')%>'; } var oJobList = { + table: null, + table_toolbar: null, + actions: [ + { + action: 'cancel', + label: '<%[ Cancel ]%>', + value: 'jobid', + callback: <%=$this->CancelJobsAction->ActiveControl->Javascript%>, + before: function() { + oBulkActionsModal.show_output(true); + } + }, + { + action: 'delete', + label: '<%[ Delete ]%>', + value: 'jobid', + callback: <%=$this->DeleteJobsAction->ActiveControl->Javascript%>, + before: function() { + oBulkActionsModal.show_output(true); + }, + validate: function(selected) { + var running_jobs = []; + selected.each(function(v, k) { + if (JobStatus.is_running(v.jobstatus)) { + running_jobs.push(' - [' + v.jobid + '] ' + v.name); + } + }); + if (running_jobs.length > 0) { + var emsg = '<%[ Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again. ]%>'; + emsg = emsg.replace('%running_jobs', '
' + running_jobs.join('
') + '
'); + oBulkActionsModal.set_error(emsg); + return false; + } + return true; + } + } + ], ids: { job_list: 'job_list', job_list_body: 'job_list_body' @@ -114,13 +151,22 @@ var oJobList = { update_job_table(this.table, this.data); } else { this.set_table(); + this.set_bulk_actions(); + this.set_events(); } }, + set_events: function() { + document.getElementById(this.ids.job_list).addEventListener('click', function(e) { + $(function() { + this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none'; + }.bind(this)); + }.bind(this)); + }, set_table: function() { this.table = $('#' + this.ids.job_list).DataTable({ data: this.data, deferRender: true, - dom: 'lBfrtip', + dom: 'lB<"table_toolbar">frtip', stateSave: true, buttons: [ 'copy', 'csv', 'colvis' @@ -349,6 +395,11 @@ var oJobList = { className: "dt-body-right", targets: [ 15, 16 ] }], + select: { + style: 'os', + selector: 'td:not(:last-child):not(:first-child)', + blurable: false + }, order: [1, 'desc'], initComplete: function () { this.api().columns([2, 3, 4, 5, 14]).every(function () { @@ -383,11 +434,46 @@ var oJobList = { }); } }); + }, + set_bulk_actions: function() { + this.table_toolbar = get_table_toolbar(this.table, this.actions, { + actions: '<%[ Actions ]%>', + ok: '<%[ OK ]%>' + }); } }; MonitorParams = {jobs: null}; $(function() { MonitorCallsInterval.push(function() { oJobList.init(); }); }); + +/** + * Defne bulk actions output id here because expression tags (< % = % >) cannot + * be defined in the TCallback ClientSide properties. + */ +var bulk_actions_output_id = '<%=$this->SourceTemplateControl->BulkActions->BulkActionsOutput->ClientID%>'; + + + oBulkActionsModal.clear_output(bulk_actions_output_id); + oBulkActionsModal.show_loader(true); + + + oBulkActionsModal.show_loader(false); + // job status in the Catalog changes a little moment after finishing cancel command + setTimeout('oMonitor()', 5000); + + + + + oBulkActionsModal.clear_output(bulk_actions_output_id); + oBulkActionsModal.show_loader(true); + + + oBulkActionsModal.show_loader(false); + oJobList.table_toolbar.style.display = 'none'; + oMonitor(); + + + diff --git a/gui/baculum/protected/Web/Pages/JobHistoryList.php b/gui/baculum/protected/Web/Pages/JobHistoryList.php index a80d7626e..4cb24b4a7 100644 --- a/gui/baculum/protected/Web/Pages/JobHistoryList.php +++ b/gui/baculum/protected/Web/Pages/JobHistoryList.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -98,4 +98,52 @@ class JobHistoryList extends BaculumWebPage { ); $this->getPage()->getCallbackClient()->callClientFunction('refresh_job_history'); } + + /** + * Cancel multiple jobs. + * Used for bulk actions. + * + * @param TCallback $sender callback object + * @param TCallbackEventPrameter $param event parameter + * @return none + */ + public function cancelJobs($sender, $param) { + $result = []; + $jobids = explode('|', $param->getCallbackParameter()); + for ($i = 0; $i < count($jobids); $i++) { + $ret = $this->getModule('api')->set( + ['jobs', intval($jobids[$i]), 'cancel'] + ); + if ($ret->error !== 0) { + $result[] = $ret->output; + break; + } + $result[] = implode(PHP_EOL, $ret->output); + } + $this->getCallbackClient()->update($this->BulkActions->BulkActionsOutput, implode(PHP_EOL, $result)); + } + + /** + * Delete multiple jobs. + * Used for bulk actions. + * + * @param TCallback $sender callback object + * @param TCallbackEventPrameter $param event parameter + * @return none + */ + public function deleteJobs($sender, $param) { + $result = []; + $jobids = explode('|', $param->getCallbackParameter()); + for ($i = 0; $i < count($jobids); $i++) { + $ret = $this->getModule('api')->remove( + ['jobs', intval($jobids[$i])] + ); + if ($ret->error !== 0) { + $result[] = $ret->output; + break; + } + $result[] = implode(PHP_EOL, $ret->output); + } + $this->getCallbackClient()->update($this->BulkActions->BulkActionsOutput, implode(PHP_EOL, $result)); + } } diff --git a/gui/baculum/protected/Web/Pages/VolumeList.page b/gui/baculum/protected/Web/Pages/VolumeList.page index d2d73e376..951c65a9a 100644 --- a/gui/baculum/protected/Web/Pages/VolumeList.page +++ b/gui/baculum/protected/Web/Pages/VolumeList.page @@ -139,18 +139,49 @@
+ + + oBulkActionsModal.clear_output(bulk_actions_output_id); + oBulkActionsModal.show_loader(true); + + + oBulkActionsModal.show_loader(false); + + + + + oBulkActionsModal.clear_output(bulk_actions_output_id); + oBulkActionsModal.show_loader(true); + + + oBulkActionsModal.show_loader(false); + + + diff --git a/gui/baculum/protected/Web/Pages/VolumeList.php b/gui/baculum/protected/Web/Pages/VolumeList.php index 528e9ad05..f0b731e09 100644 --- a/gui/baculum/protected/Web/Pages/VolumeList.php +++ b/gui/baculum/protected/Web/Pages/VolumeList.php @@ -3,7 +3,7 @@ * Bacula(R) - The Network Backup Solution * Baculum - Bacula web interface * - * Copyright (C) 2013-2019 Kern Sibbald + * Copyright (C) 2013-2020 Kern Sibbald * * The main author of Baculum is Marcin Haba. * The original author of Bacula is Kern Sibbald, with contributions @@ -55,5 +55,53 @@ class VolumeList extends BaculumWebPage { self::USE_CACHE )->output; } + + /** + * Prune multiple volumes. + * Used for bulk actions. + * + * @param TCallback $sender callback object + * @param TCallbackEventPrameter $param event parameter + * @return none + */ + public function pruneVolumes($sender, $param) { + $result = []; + $mediaids = explode('|', $param->getCallbackParameter()); + for ($i = 0; $i < count($mediaids); $i++) { + $ret = $this->getModule('api')->set( + ['volumes', intval($mediaids[$i]), 'prune'] + ); + if ($ret->error !== 0) { + $result[] = $ret->output; + break; + } + $result[] = implode(PHP_EOL, $ret->output); + } + $this->getCallbackClient()->update($this->BulkActions->BulkActionsOutput, implode(PHP_EOL, $result)); + } + + /** + * Purge multiple volumes. + * Used for bulk actions. + * + * @param TCallback $sender callback object + * @param TCallbackEventPrameter $param event parameter + * @return none + */ + public function purgeVolumes($sender, $param) { + $result = []; + $mediaids = explode('|', $param->getCallbackParameter()); + for ($i = 0; $i < count($mediaids); $i++) { + $ret = $this->getModule('api')->set( + ['volumes', intval($mediaids[$i]), 'purge'] + ); + if ($ret->error !== 0) { + $result[] = $ret->output; + break; + } + $result[] = implode(PHP_EOL, $ret->output); + } + $this->getCallbackClient()->update($this->BulkActions->BulkActionsOutput, implode(PHP_EOL, $result)); + } } ?> diff --git a/gui/baculum/protected/Web/Portlets/BulkActionsModal.php b/gui/baculum/protected/Web/Portlets/BulkActionsModal.php new file mode 100644 index 000000000..efb3955d6 --- /dev/null +++ b/gui/baculum/protected/Web/Portlets/BulkActionsModal.php @@ -0,0 +1,54 @@ + + * @category Control + * @package Baculum Web + */ +class BulkActionsModal extends Portlets { + + const REFRESH_PAGE_BTN = 'RefreshPageBtn'; + + public function onLoad($param) { + parent::onLoad($param); + + if ($this->getRefreshPageBtn()) { + $this->getPage()->getCallbackClient()->show('bulk_actions_refresh_page'); + } + } + + public function setRefreshPageBtn($refresh_page_btn) { + $refresh_page_btn = TPropertyValue::ensureBoolean($refresh_page_btn); + $this->setViewState(self::REFRESH_PAGE_BTN, $refresh_page_btn); + } + + public function getRefreshPageBtn() { + return $this->getViewState(self::REFRESH_PAGE_BTN); + } +} +?> diff --git a/gui/baculum/protected/Web/Portlets/BulkActionsModal.tpl b/gui/baculum/protected/Web/Portlets/BulkActionsModal.tpl new file mode 100644 index 000000000..14501aef7 --- /dev/null +++ b/gui/baculum/protected/Web/Portlets/BulkActionsModal.tpl @@ -0,0 +1,58 @@ + + + diff --git a/gui/baculum/themes/Baculum-v2/css/baculum.css b/gui/baculum/themes/Baculum-v2/css/baculum.css index 486f2f8de..574281ac2 100644 --- a/gui/baculum/themes/Baculum-v2/css/baculum.css +++ b/gui/baculum/themes/Baculum-v2/css/baculum.css @@ -55,6 +55,40 @@ table.dataTable.no-footer { margin-right: 5px; } +.dataTables_wrapper select { + padding: 0.5em; + border: 1px solid #999; + border-radius: 2px; + background-color: #e9e9e9; + background-image: linear-gradient(to bottom, white 0%, #e9e9e9 100%); +} + +div.table_toolbar { + display: inline-block; + margin-left: 10px; +} + +@media screen and (max-width: 640px) { + div.table_toolbar { + display: block !important; + text-align: center; + } +} + +div.table_toolbar select { + font-weight: bold; + margin-left: 0.333em; + margin-right: 2px; +} + +table.dataTable tbody > tr.selected, table.dataTable tbody > tr > .selected { + background-color: #f5a55b !important; +} + +.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover { + background-color: #ffcf9e !important; +} + th.action_col, td.action_col { width: 110px !important; }