]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Add bulk actions for job history and volume tables
authorMarcin Haba <marcin.haba@bacula.pl>
Sun, 1 Mar 2020 16:13:15 +0000 (17:13 +0100)
committerMarcin Haba <marcin.haba@bacula.pl>
Sun, 1 Mar 2020 17:09:46 +0000 (18:09 +0100)
- add 'cancel' and 'delete' actions to job history table
- add 'prune' and 'purge' action to volume table

18 files changed:
gui/baculum/protected/Common/Class/BClientScript.php
gui/baculum/protected/Web/JavaScript/misc.js
gui/baculum/protected/Web/Lang/en/messages.mo
gui/baculum/protected/Web/Lang/en/messages.po
gui/baculum/protected/Web/Lang/ja/messages.mo
gui/baculum/protected/Web/Lang/ja/messages.po
gui/baculum/protected/Web/Lang/pl/messages.mo
gui/baculum/protected/Web/Lang/pl/messages.po
gui/baculum/protected/Web/Lang/pt/messages.mo
gui/baculum/protected/Web/Lang/pt/messages.po
gui/baculum/protected/Web/Layouts/Main.tpl
gui/baculum/protected/Web/Pages/JobHistoryList.page
gui/baculum/protected/Web/Pages/JobHistoryList.php
gui/baculum/protected/Web/Pages/VolumeList.page
gui/baculum/protected/Web/Pages/VolumeList.php
gui/baculum/protected/Web/Portlets/BulkActionsModal.php [new file with mode: 0644]
gui/baculum/protected/Web/Portlets/BulkActionsModal.tpl [new file with mode: 0644]
gui/baculum/themes/Baculum-v2/css/baculum.css

index 33b56e8fe6f05e0efbb617227f55f449327fdedb..baf6878b546e990298f472d01a1ad972c52757aa 100644 (file)
@@ -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()
        {
index 020dff3ce3e82f3fc8b2e281d0a56c1faba5fd0f..5c5342efe4d259e422682cc3ca7a1480eedd2fb7 100644 (file)
@@ -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();
index f07a72705eb783cd83ebc36a9bb50c2f4d59d82c..ba3772519eb27cb9fd4de28e3f8473a5f2e9c949 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ
index 154e18560786504ac7230078ff24ccd4886be397..cbca887ecec1d1fe4892d8007a37608b813ed11d 100644 (file)
@@ -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"
index a9ee1e6346062610fa9635e44cfcc1693ed2574f..b52cef41ef914ce208254dda47b2a777991a578a 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ
index 35e385f333f8d244183a949d62acd90b1417beb9..f80bfed396ed02510b76f43cd1c0bdbbd24c9877 100644 (file)
@@ -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"
index ae0c57db3a97955af0a178e4c9ad71c867457a38..b70a33bfa1633648441f27625212c89dc52e4ccf 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ
index ede8d7a86bc1fa7805fa98e947c522d5ca5c3c76..582b6202bd1547a859b883eee198854ae7faa552 100644 (file)
@@ -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ę"
index 564e2ac9e73ae03f453cf623a16adeeeb1558096..2c925d3c801be9f347966738b501ceae336f8d31 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ
index 3436ef1896e0a305a5e5f29c5143022c21693d52..4881fa7766cc0f694bbddeccb2032d98eeb99a89 100644 (file)
@@ -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"
index 2abc57f6f18cd2c40c11cd08e2f7f52b528c9ae0..72115998025d072cd16fe93791cb68be34c34692 100644 (file)
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/datatables.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.responsive.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/responsive.jqueryui.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.buttons.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.html5.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.colVis.js %> />
+                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.select.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/excanvas.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/bacula-config.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/misc.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/graph.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/statistics.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.buttons.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.html5.js %> />
-                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.colVis.js %> />
                        <!-- Top container -->
                        <div class="w3-bar w3-top w3-black w3-large" style="z-index:4">
                                <button type="button" class="w3-bar-item w3-button w3-hover-none w3-hover-text-light-grey" onclick="W3SideBar.open();"><i class="fa fa-bars"></i>  Menu</button>
index 01f3b0907a1fec25da702c8b002c834f2dc815a0..267c4dcf587ec763b33563a323f0ab90b9016f9f 100644 (file)
@@ -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', '<hr />' + running_jobs.join('<br />') + '<hr />');
+                                       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%>';
 </script>
+<com:TCallback ID="CancelJobsAction" OnCallback="cancelJobs">
+       <prop:ClientSide.OnLoading>
+               oBulkActionsModal.clear_output(bulk_actions_output_id);
+               oBulkActionsModal.show_loader(true);
+       </prop:ClientSide.OnLoading>
+       <prop:ClientSide.OnComplete>
+               oBulkActionsModal.show_loader(false);
+               // job status in the Catalog changes a little moment after finishing cancel command
+               setTimeout('oMonitor()', 5000);
+       </prop:ClientSide.OnComplete>
+</com:TCallback>
+<com:TCallback ID="DeleteJobsAction" OnCallback="deleteJobs">
+       <prop:ClientSide.OnLoading>
+               oBulkActionsModal.clear_output(bulk_actions_output_id);
+               oBulkActionsModal.show_loader(true);
+       </prop:ClientSide.OnLoading>
+       <prop:ClientSide.OnComplete>
+               oBulkActionsModal.show_loader(false);
+               oJobList.table_toolbar.style.display = 'none';
+               oMonitor();
+       </prop:ClientSide.OnComplete>
+</com:TCallback>
+<com:Application.Web.Portlets.BulkActionsModal ID="BulkActions" />
 </com:TContent>
index a80d7626eef0f66f8e162ca934d33ba0854f6a76..4cb24b4a7eebef0d126ba18b4edcea9e5eb24302 100644 (file)
@@ -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));
+       }
 }
index d2d73e3766dbb5d5653fdf5439c92c5d7cedd5c7..951c65a9a6fcd21aebb246cce78d38af7e40cd40 100644 (file)
        </div>
 <script type="text/javascript">
 var oVolumeList = {
+       table: null,
+       table_toolbar: null,
+       actions: [
+               {
+                       action: 'prune',
+                       label: '<%[ Prune ]%>',
+                       value: 'mediaid',
+                       callback: <%=$this->PruneVolumesAction->ActiveControl->Javascript%>,
+                       before: function() {
+                               oBulkActionsModal.show_output(true);
+                       }
+               },
+               {
+                       action: 'purge',
+                       label: '<%[ Purge ]%>',
+                       value: 'mediaid',
+                       callback: <%=$this->PurgeVolumesAction->ActiveControl->Javascript%>,
+                       before: function() {
+                               oBulkActionsModal.show_output(true);
+                       }
+               }
+       ],
        ids: {
                volume_list: 'volume_list',
                volume_list_body: 'volume_list_body'
        },
        init: function() {
                this.set_table();
+               this.set_bulk_actions();
+               this.set_events();
+       },
+       set_events: function() {
+               document.getElementById(this.ids.volume_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() {
-               var table = $('#' + this.ids.volume_list).DataTable({
+               this.table = $('#' + this.ids.volume_list).DataTable({
                        data: <%=json_encode($this->volumes)%>,
                        deferRender: true,
-                       dom: 'lBfrtip',
+                       dom: 'lB<"table_toolbar">frtip',
                        stateSave: true,
                        buttons: [
                                'copy', 'csv', 'colvis'
@@ -513,6 +544,11 @@ var oVolumeList = {
                                className: "dt-body-right",
                                targets: [ 18 ]
                        }],
+                       select: {
+                               style:    'os',
+                               selector: 'td:not(:last-child):not(:first-child)',
+                               blurable: false
+                       },
                        order: [3, 'asc'],
                        initComplete: function () {
                                this.api().columns([2, 3, 6, 10, 13]).every(function () {
@@ -547,8 +583,41 @@ var oVolumeList = {
                                });
                        }
                });
+       },
+       set_bulk_actions: function() {
+               this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+                       actions: '<%[ Actions ]%>',
+                       ok: '<%[ OK ]%>'
+               });
        }
 };
 oVolumeList.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%>';
 </script>
+<com:TCallback ID="PruneVolumesAction" OnCallback="pruneVolumes">
+       <prop:ClientSide.OnLoading>
+               oBulkActionsModal.clear_output(bulk_actions_output_id);
+               oBulkActionsModal.show_loader(true);
+       </prop:ClientSide.OnLoading>
+       <prop:ClientSide.OnComplete>
+               oBulkActionsModal.show_loader(false);
+       </prop:ClientSide.OnComplete>
+</com:TCallback>
+<com:TCallback ID="PurgeVolumesAction" OnCallback="purgeVolumes">
+       <prop:ClientSide.OnLoading>
+               oBulkActionsModal.clear_output(bulk_actions_output_id);
+               oBulkActionsModal.show_loader(true);
+       </prop:ClientSide.OnLoading>
+       <prop:ClientSide.OnComplete>
+               oBulkActionsModal.show_loader(false);
+       </prop:ClientSide.OnComplete>
+</com:TCallback>
+<com:Application.Web.Portlets.BulkActionsModal
+       ID="BulkActions"
+       RefreshPageBtn="true"
+       />
 </com:TContent>
index 528e9ad05f836d0728b05f217f715b5b10b60a2b..f0b731e09a0188ab21c38edb1d33018a1e43071c 100644 (file)
@@ -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 (file)
index 0000000..efb3955
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('System.Web.UI.ActiveControls.TActiveLabel');
+Prado::using('Application.Web.Portlets.Portlets');
+
+/**
+ * Bulk actions modal control.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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 (file)
index 0000000..14501ae
--- /dev/null
@@ -0,0 +1,58 @@
+<div id="bulk_actions_modal" class="w3-modal" style="display: none">
+       <div class="w3-modal-content w3-card-4 w3-animate-zoom" style="width: 830px">
+               <header class="w3-container w3-green">
+                       <span onclick="oBulkActionsModal.show_output(false);" class="w3-button w3-display-topright">×</span>
+                       <h2><%[ Bulk action ]%></h2>
+               </header>
+               <div class="w3-margin-left w3-margin-right" style="max-height: 400px; overflow-x: auto;">
+                       <div class="w3-code">
+                               <pre><com:TActiveLabel ID="BulkActionsOutput" /></pre>
+                       </div>
+               </div>
+               <footer class="w3-container w3-center w3-border-top">
+                       <button type="button" class="w3-button w3-section w3-green" onclick="oBulkActionsModal.show_output(false);"><i class="fa fa-check"></i> &nbsp;<%[ OK ]%></button>
+                       <button id="bulk_actions_refresh_page" type="button" class="w3-button w3-section w3-green" style="display: none" onclick="oBulkActionsModal.refresh_page();"><i class="fas fa-redo-alt"></i> &nbsp;<%[ Refresh page ]%></button>
+                       <i id="bulk_actions_loader" class="fa fa-sync w3-spin w3-margin-left"></i>
+               </footer>
+       </div>
+</div>
+<div id="bulk_actions_validation_error_modal" class="w3-modal" style="display: none">
+       <div class="w3-modal-content w3-card-4 w3-animate-zoom" style="width: 600px">
+               <header class="w3-container w3-red">
+                       <span onclick="oBulkActionsModal.show_error(false);" class="w3-button w3-display-topright">×</span>
+                       <h2><%[ Validation error ]%></h2>
+               </header>
+               <div class="w3-margin-left w3-margin-right" style="max-height: 400px; overflow-x: auto;">
+                       <p id="bulk_actions_validation_error_txt"></p>
+               </div>
+               <footer class="w3-container w3-center w3-border-top">
+                       <button type="button" class="w3-button w3-section w3-green" onclick="oBulkActionsModal.show_error(false);"><i class="fas fa-check"></i> &nbsp;<%[ OK ]%></button>
+               </footer>
+       </div>
+</div>
+<script>
+var oBulkActionsModal = {
+       show_loader: function(show) {
+               document.getElementById('bulk_actions_loader').style.display = show ? '' : 'none';
+       },
+       clear_output: function(id) {
+               document.getElementById(id).textContent = '';
+       },
+       show_output: function(show) {
+               document.getElementById('bulk_actions_modal').style.display = show ? 'block' : '';
+       },
+       set_error: function(emsg) {
+               document.getElementById('bulk_actions_validation_error_txt').innerHTML = emsg;
+               this.show_error(true);
+       },
+       show_error: function(show) {
+               document.getElementById('bulk_actions_validation_error_modal').style.display = show ? 'block' : '';
+       },
+       refresh_page: function() {
+               document.location.reload();
+       },
+       show_refresh_btn: function(show) {
+               document.getElementById('bulk_actions_refresh_page').style.display = show ? 'block' : '';
+       },
+};
+</script>
index 486f2f8de03de2f708d69f2c8a86c23a5a580f1c..574281ac276fb31ea0b26e4b524c6267fd612629 100644 (file)
@@ -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;
 }