]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Add searching jobs by filename in restore wizard - idea proposed by Wanderle...
authorMarcin Haba <marcin.haba@bacula.pl>
Sun, 25 Oct 2020 06:41:25 +0000 (07:41 +0100)
committerMarcin Haba <marcin.haba@bacula.pl>
Sun, 25 Oct 2020 06:41:25 +0000 (07:41 +0100)
12 files changed:
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/Wizard.tpl
gui/baculum/protected/Web/Pages/RestoreWizard.page
gui/baculum/protected/Web/Pages/RestoreWizard.php
gui/baculum/themes/Baculum-v2/css/baculum.css

index 368f5a97d8de2773c056f4027548930ca6165310..01d671f5f0db05e035cc0f45b756a1e70cae196c 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 c4e676d7015a6bb6f8875e0fbd797388cb41e67e..936ee4629f0bf805054d96a2701a0c7fb5a251b5 100644 (file)
@@ -3061,3 +3061,15 @@ msgstr "The API hosts define connection parameters to hosts with the Baculum API
 
 msgid "Set all CommandAcls used by Baculum Web"
 msgstr "Set all CommandAcls used by Baculum Web"
+
+msgid "Find job by filename (without path):"
+msgstr "Find job by filename (without path):"
+
+msgid "File"
+msgstr "File"
+
+msgid "match exact filename"
+msgstr "match exact filename"
+
+msgid "With this option are searched files with names equal provided filename, otherwise there are searched files with names containing provided filename like *filename*."
+msgstr "With this option are searched files with names equal provided filename, otherwise there are searched files with names containing provided filename like *filename*."
index 51c51b8f4701f6271dfb038ba6fd08a95eb09cad..b74b610fec1aea79204523b1391ca43220f39e25 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 51cb5ae7e0c5d88608c821e50404f4e7e2ccb3ce..2400ae006623d8f91cae489f42e9798b4daab5ee 100644 (file)
@@ -3147,3 +3147,15 @@ msgstr "The API hosts define connection parameters to hosts with the Baculum API
 
 msgid "Set all CommandAcls used by Baculum Web"
 msgstr "Set all CommandAcls used by Baculum Web"
+
+msgid "Find job by filename (without path):"
+msgstr "Find job by filename (without path):"
+
+msgid "File"
+msgstr "File"
+
+msgid "match exact filename"
+msgstr "match exact filename"
+
+msgid "With this option are searched files with names equal provided filename, otherwise there are searched files with names containing provided filename like *filename*."
+msgstr "With this option are searched files with names equal provided filename, otherwise there are searched files with names containing provided filename like *filename*."
index bbf162e61b8a2a81154e84e59b6a0268bedc9c02..77e676a6d5e8395291bc7b0703b836052454c56a 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 8f4aef09b17270877e246fd3110b6656b90b5739..2ba865292da820fdcbdd5c682eb289a2588e4dc4 100644 (file)
@@ -3072,3 +3072,15 @@ msgstr "Hosty API definiują parametry połączenia do hostów z instancjami Bac
 
 msgid "Set all CommandAcls used by Baculum Web"
 msgstr "Ustaw wszystkie wartości CommandAcl używane przez Baculum Web"
+
+msgid "Find job by filename (without path):"
+msgstr "Znajdź zadanie według nazwy pliku (bez ścieżki):"
+
+msgid "File"
+msgstr "Plik"
+
+msgid "match exact filename"
+msgstr "dopasuj dokładną nazwę pliku"
+
+msgid "With this option are searched files with names equal provided filename, otherwise there are searched files with names containing provided filename like *filename*."
+msgstr "Z tą opcją są przeszukiwane pliki z nazwą równą dostarczonej nazwie pliku, w przeciwnym razie są wyszukiwane pliki z nazwami zawierającymi podaną nazwę pliku, np. *nazwapliku*."
index 4f3c2f91fab1084433736c104f35f7700b4a42f1..1dba63d4b75d07e19599044fc07013b022c22904 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 216858d2ba4bb983b9af0258b087c5bef24ca58d..2eed72e1848ab930a81612c7cf0acb14b1705cfc 100644 (file)
@@ -3072,3 +3072,14 @@ msgstr "O host de API define parâmetros de conexão para hosts com as instânci
 msgid "Set all CommandAcls used by Baculum Web"
 msgstr "Definir todos os comandos ACL's usados pelo Baculum"
 
+msgid "Find job by filename (without path):"
+msgstr "Find job by filename (without path):"
+
+msgid "File"
+msgstr "File"
+
+msgid "match exact filename"
+msgstr "match exact filename"
+
+msgid "With this option are searched files with names equal provided filename, otherwise there are searched files with names containing provided filename like *filename*."
+msgstr "With this option are searched files with names equal provided filename, otherwise there are searched files with names containing provided filename like *filename*."
index 2f443e99bfc9ac63a052d6924c6012c48d9b7716..a06ec91545294cd07fdf0f82405fd9a6ce5a0a6b 100644 (file)
@@ -16,6 +16,9 @@
                        <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/opentip.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/tooltip.js %> />
                        <com:BClientScript ScriptUrl=<%~ ../JavaScript/misc.js %> />
index 14e8fa6820ed76b33cd998758bbc0835557f8015..df85974b4556f6baa9de471e4d14b1856f6edefa 100644 (file)
                                        <i class="fa fa-angle-left"></i> &nbsp;<%[ Previous ]%>
                                </com:TLinkButton>
                                <com:TLinkButton
+                                       ID="NextStepBtn"
                                        CommandName="NextStep"
                                        CssClass="w3-button w3-green"
-                                       Visible="<%=($this->getPage()->RestoreWizard->ActiveStepIndex != 1 || ($this->getPage()->RestoreWizard->ActiveStepIndex == 1 && count($this->getPage()->jobs_to_restore) > 0))%>"
+                                       Display="<%=($this->getPage()->RestoreWizard->ActiveStepIndex != 1) ? 'Dynamic' : 'None'%>"
                                >
                                        <%[ Next ]%>&nbsp; <i class="fa fa-angle-right"></i>
                                </com:TLinkButton>
                                        />
                                        <com:TLabel ForControl="GroupBackupSelection" Text="<%[ Group most recent backups ]%>" /></div>
                        </div>
-                       <com:TLabel Text="<%[ There is no backup for restore. Please go to previous step and select another client for restore or proceed backups for the client selected in previous step. ]%>" CssClass="w3-text-red" Visible="<%=(count($this->getPage()->jobs_to_restore) === 0 && $this->GroupBackupToRestore->ItemCount == 0)%>"/>
+                       <span id="no_backup_to_restore_msg" class="w3-text-red" style="display: none"><%[ There is no backup for restore. Please go to previous step and select another client for restore or proceed backups for the client selected in previous step. ]%></span>
                        <div id="backup_to_restore_field" style="display: <%=!$this->OnlySelectedBackupSelection->Checked ? 'none' : 'block'%>">
                                <p><strong><%[ Note: if you select incremental or differential backup, on the next step will be also loaded all directories and files from older backups required to do the job restore. In other words, the selected backup determines time point from which will be loaded the selected backup and other older backups (incremental, differential) backups up till closest full backup. ]%></strong></p>
+                               <div id="table_filters_body">
+                                       <span class="text"><%[ Find job by filename (without path): ]%></span> <input type="text" class="w3-text" id="restore_wizard_find_job_by_filename" /> <i id="restore_wizard_find_job_by_filename_btn" class="fas fa-search"  style="cursor: pointer"></i>
+                                       <span  title="<%[ With this option are searched files with names equal provided filename, otherwise there are searched files with names containing provided filename like *filename*. ]%>" style="margin-left: 10px"><input type="checkbox" class="w3-check" id="restore_wizard_find_strict" value="1" /> <span class="text"><%[ match exact filename ]%></span></span> &nbsp;<i id="restore_wizard_find_job_by_filename_loader" class="fas fa-sync fa-spin" style="display: none"></i>
+                               </div>
                                <table id="job_to_restore_list" style="width: 100%">
                                        <thead>
                                                <tr>
                                                        <th></th>
                                                        <th>JobId</th>
                                                        <th><%[ Job name ]%></th>
+                                                       <th><%[ File ]%></th>
                                                        <th><%[ Type ]%></th>
                                                        <th><%[ Level ]%></th>
                                                        <th><%[ Job status ]%></th>
                                                        <th><%[ Size ]%></th>
                                                        <th><%[ Files ]%></th>
+                                                       <th><%[ Start time ]%></th>
                                                        <th><%[ End time ]%></th>
                                                        <th><%[ Select ]%></th>
                                                </tr>
                                                        <th></th>
                                                        <th>JobId</th>
                                                        <th><%[ Job name ]%></th>
+                                                       <th><%[ File ]%></th>
                                                        <th><%[ Type ]%></th>
                                                        <th><%[ Level ]%></th>
                                                        <th><%[ Job status ]%></th>
                                                        <th><%[ Size ]%></th>
                                                        <th><%[ Files ]%></th>
+                                                       <th><%[ Start time ]%></th>
                                                        <th><%[ End time ]%></th>
                                                        <th><%[ Select ]%></th>
                                                </tr>
                                        </tfoot>
                                </table>
+<com:TCallback
+       ID="JobListCb"
+       OnCallback="loadJobList"
+>
+       <prop:ClientSide.OnLoading>
+               oJobsToRestoreList.show_find_job_by_filename_loader(true);
+       </prop:ClientSide.OnLoading>
+       <prop:ClientSide.OnComplete>
+               oJobsToRestoreList.show_find_job_by_filename_loader(false);
+               <%=$this->Session->contains('restore_jobid') ? 'oJobsToRestoreList.set_jobid(' . $this->Session['restore_jobid'] . ');' : ''%>
+       </prop:ClientSide.OnComplete>
+</com:TCallback>
 <script type="text/javascript">
 var oJobsToRestoreList = {
        ids: {
                job_list: 'job_to_restore_list',
-               job_list_body: 'job_to_restore_list_body'
+               job_list_body: 'job_to_restore_list_body',
+               filters: 'table_filters',
+               filters_body: 'table_filters_body',
+               find_job_by_filename: 'restore_wizard_find_job_by_filename',
+               find_job_by_filename_btn: 'restore_wizard_find_job_by_filename_btn',
+               find_job_by_filename_loader: 'restore_wizard_find_job_by_filename_loader',
+               find_strict: 'restore_wizard_find_strict',
+               no_backup_msg: 'no_backup_to_restore_msg'
        },
-       init: function() {
-               this.set_table();
+       data: [],
+       opts: {},
+       table: null,
+       initialize: true,
+       init: function(opts) {
+               this.opts = opts;
+               if (this.table) {
+                       var page = this.table.page();
+                       this.table.clear().rows.add(this.data).draw();
+                       this.initialize = false;
+                       this.table.page(page).draw(false);
+               } else {
+                       this.set_table();
+                       this.set_filters();
+                       this.set_events();
+                       this.set_msgs();
+               }
+               this.set_btns();
+               this.set_column_visibility();
+       },
+       set_events: function() {
+               document.getElementById(this.ids.find_job_by_filename).addEventListener('keyup', function(e) {
+                       if (e.keyCode == 13) {
+                               e.preventDefault();
+                               this.load_jobs();
+                       }
+               }.bind(this));
+               document.getElementById(this.ids.find_job_by_filename_btn).addEventListener('click', function(e) {
+                       this.load_jobs();
+               }.bind(this));
+       },
+       load_jobs: function() {
+               var cb = <%=$this->JobListCb->ActiveControl->Javascript%>;
+               var fjbf = document.getElementById(this.ids.find_job_by_filename);
+               var fs = document.getElementById(this.ids.find_strict);
+               cb.setCallbackParameter({
+                       filename: fjbf.value,
+                       strict: fs.checked
+               });
+               cb.dispatch();
+       },
+       show_find_job_by_filename_loader: function(show) {
+               var loader = document.getElementById(this.ids.find_job_by_filename_loader);
+               loader.style.display = (show) ? '' : 'none';
+       },
+       update_table: function(data, list_type) {
+               oJobsToRestoreList.data = data;
+               oJobsToRestoreList.init({
+                       list_type: list_type
+               });
+       },
+       set_column_visibility: function() {
+               if (!this.opts.hasOwnProperty('list_type')) {
+                       return;
+               }
+               if (this.opts.list_type == <%=RestoreWizard::JOB_LIST_BY_CLIENT%>) {
+                       this.table.columns([4, 5, 9, 10]).visible(true);
+                       this.table.column(3).visible(false);
+                       this.table.columns.adjust();
+               } else if (this.opts.list_type == <%=RestoreWizard::JOB_LIST_BY_FILENAME%>) {
+                       this.table.columns([4, 5, 9, 10]).visible(false);
+                       this.table.column(3).visible(true);
+                       this.table.columns.adjust();
+               }
+       },
+       set_msgs: function() {
+               if (this.initialize) {
+                       var group_backup = document.getElementById('<%=$this->GroupBackupToRestore->ClientID%>');
+                       if (this.data.length == 0 && group_backup.options.length == 0) {
+                               document.getElementById(this.ids.no_backup_msg).style.display = '';
+                       }
+               }
+       },
+       set_btns: function() {
+               var active_step_idx = '<%=$this->RestoreWizard->ActiveStepIndex%>';
+               var next_step = document.getElementById('<%=$this->RestoreWizard->getStepNavigation()->NextStepBtn->ClientID%>');
+               if (active_step_idx != 1 || this.data.length > 0) {
+                       next_step.style.display = '';
+               } else {
+                       next_step.style.display = 'none';
+               }
+       },
+       set_jobid: function(jobid) {
+               if (!this.table) {
+                       return;
+               }
+               var data_len = this.table.data().length;
+               var page_len = this.table.page.info().length; // data length on the page
+               if (data_len > page_len) {
+                       //var sel_node = this.table.row({jobid: jobid}).node();
+                       //var node_pos = this.table.rows({order: 'current'}).nodes().indexOf(sel_node);
+                       var node_pos = -1;
+                       this.table.rows({
+                               order: 'applied',
+                               filter: 'applied'
+                       }).data().filter(function(value, index) {
+                               if (value.jobid == jobid) {
+                                       node_pos = index;
+                                       return true;
+                               }
+                               return false;
+                       });
+                       if (node_pos != -1) {
+                               var page_number = Math.floor(node_pos / page_len);
+                               this.table.page(page_number).draw(false);
+                       }
+               }
        },
        set_table: function() {
-               var table = $('#' + this.ids.job_list).DataTable({
-                       data: <%=json_encode(array_values($this->getPage()->jobs_to_restore))%>,
+               this.table = $('#' + this.ids.job_list).DataTable({
+                       data: this.data,
                        deferRender: true,
+                       dom: 'lB<"#' + this.ids.filters + '">frtip',
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
                        stateSave: true,
                        columns: [
                                {
@@ -247,6 +384,26 @@ var oJobsToRestoreList = {
                                                return ret;
                                        }
                                },
+                               {
+                                       data: 'file',
+                                       responsivePriority: 11,
+                                       visible: false,
+                                       width: '50%',
+                                       render: function(data, type, row) {
+                                               var ret = data;
+                                               if (type === 'display') {
+                                                       var span = document.createElement('SPAN');
+                                                       span.title = data;
+                                                       if (data.length > 111) {
+                                                               span.textContent = data.substr(0, 52) + ' (...) ' + data.substr(-52);
+                                                       } else {
+                                                               span.textContent = data;
+                                                       }
+                                                       ret = span.outerHTML;
+                                               }
+                                               return ret;
+                                       },
+                               },
                                {
                                        data: 'type',
                                        render: function(data, type, row) {
@@ -275,6 +432,12 @@ var oJobsToRestoreList = {
                                        data: 'jobfiles',
                                        responsivePriority: 8
                                },
+                               {
+                                       data: 'starttime',
+                                       render: render_date,
+                                       visible: false,
+                                       responsivePriority: 10
+                               },
                                {
                                        data: 'endtime',
                                        render: render_date,
@@ -314,15 +477,15 @@ var oJobsToRestoreList = {
                        },
                        {
                                className: "dt-center",
-                               targets: [ 1, 3, 4, 5, 7, 8, 9 ]
+                               targets: [ 1, 4, 5, 6, 8, 9, 10, 11 ]
                        },
                        {
                                className: "dt-body-right",
-                               targets: [ 6 ]
+                               targets: [ 7 ]
                        }],
                        order: [1, 'desc'],
                        initComplete: function () {
-                               this.api().columns([2, 3, 4, 5]).every(function () {
+                               this.api().columns([2, 4, 5, 6]).every(function () {
                                        var column = this;
                                        var select = $('<select><option value=""></option></select>')
                                        .appendTo($(column.footer()).empty())
@@ -334,21 +497,36 @@ var oJobsToRestoreList = {
                                                .search(val ? '^' + val + '$' : '', true, false)
                                                .draw();
                                        });
-                                       if (column[0][0] === 2 || column[0][0] == 5) {
+                                       if (column[0][0] === 2 || column[0][0] == 6) {
                                                column.data().unique().sort().each(function (d, j) {
-                                                       select.append('<option value="' + d + '" title="' + JobStatus.get_desc(d) + '">' + d + '</option>');
+                                                       if (column.search() == '^' + dtEscapeRegex(d) + '$') {
+                                                               select.append('<option value="' + d + '" title="' + JobStatus.get_desc(d) + '" selected>' + d + '</option>');
+                                                       } else {
+                                                               select.append('<option value="' + d + '" title="' + JobStatus.get_desc(d) + '">' + d + '</option>');
+                                                       }
                                                });
                                        } else {
                                                column.cells('', column[0]).render('display').unique().sort().each(function(d, j) {
-                                                       select.append('<option value="' + d + '">' + d + '</option>');
+                                                       if (column.search() == '^' + dtEscapeRegex(d) + '$') {
+                                                               select.append('<option value="' + d + '" selected>' + d + '</option>'); 
+                                                       } else {
+                                                               select.append('<option value="' + d + '">' + d + '</option>');  
+                                                       }
                                                });
                                        }
                                });
                        }
                });
+       },
+       set_filters: function() {
+               var filters = document.getElementById(this.ids.filters);
+               var filters_body = document.getElementById(this.ids.filters_body);
+               filters.appendChild(filters_body);
        }
 };
-oJobsToRestoreList.init();
+$(function() {
+       oJobsToRestoreList.load_jobs();
+});
 </script>
                        </div>
                        <div id="group_backup_to_restore_field" style="display: <%=!$this->GroupBackupSelection->Checked ? 'none' : 'block'%>">
index 67f20faf5b6fe82248242024ae79cafba604bf42..559e8de485a0e301eb3a2ce50777dacececb81ea 100644 (file)
@@ -51,6 +51,9 @@ class RestoreWizard extends BaculumWebPage
         */
        private $jobstatus = ['T', 'W', 'A', 'E', 'e', 'f'];
 
+       const JOB_LIST_BY_CLIENT = 1;
+       const JOB_LIST_BY_FILENAME = 2;
+
        /**
         * File browser special directories.
         */
@@ -306,7 +309,67 @@ class RestoreWizard extends BaculumWebPage
                        ['clients', $clientid, 'jobs']
                )->output;
                $jobs = $this->getModule('misc')->objectToArray($jobs_for_client);
-               $this->jobs_to_restore = array_filter($jobs, [$this, 'isJobToRestore']);
+               function add_file($item) {
+                       $item['file'] = '';
+                       return $item;
+               }
+               $jobs = array_map('add_file', $jobs);
+               return array_filter($jobs, [$this, 'isJobToRestore']);
+       }
+
+       /**
+        * Load backups for selected client by filename (Step 2).
+        *
+        * @param string $filename filename to find a backup
+        * @param boolean $strict strict mode with exact matching name == filename
+        * @return array job list with files
+        */
+       private function loadBackupsByFilename($filename, $strict) {
+               $clientid = $this->BackupClient->SelectedValue;
+               $query = [
+                       'clientid' => $clientid,
+                       'filename' => rawurlencode($filename),
+                       'strict' => $strict
+               ];
+               $params = [
+                       'jobs',
+                       'files',
+                       '?' . http_build_query($query)
+               ];
+               $result = $this->getModule('api')->get($params);
+               $ret = [];
+               if ($result->error == 0) {
+                       $jobs = $this->getModule('misc')->objectToArray($result->output);
+                       $ret = array_filter($jobs, [$this, 'isJobToRestore']);
+               }
+               return $ret;
+       }
+
+       /**
+        * Load job list.
+        * Common method both for loading job list for a client and for job list displayed
+        * after providing filename saved in backup.
+        * It is responsible for loading job list to select by user for restore.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param param object
+        * @return none
+        */
+       public function loadJobList($sender, $param) {
+               $prop = $param->getCallbackParameter();
+               $jobs = [];
+               $list_type = self::JOB_LIST_BY_CLIENT;
+               if (is_object($prop) && !empty($prop->filename)) {
+                       $list_type = self::JOB_LIST_BY_FILENAME;
+                       $jobs = $this->loadBackupsByFilename($prop->filename, $prop->strict);
+               } else {
+                       $list_type = self::JOB_LIST_BY_CLIENT;
+                       $jobs = $this->loadBackupsForClient();
+               }
+               $this->getCallbackClient()->callClientFunction(
+                       'oJobsToRestoreList.update_table',
+                       [array_values($jobs), $list_type]
+               );
        }
 
        /**
index 366f3c793ae7cae256a9bb080097d642bc753743..c8a6550f86c4d3dcc8545cbfd095398c3de9d608 100644 (file)
@@ -133,6 +133,20 @@ div.table_toolbar select {
        margin-right: 2px;
 }
 
+#table_filters {
+       display: inline-block;
+       vertical-align: text-top;
+}
+
+#table_filters span.text {
+       font-size: 12px;
+}
+
+#table_filters input, span.text {
+       vertical-align: middle;
+       top: 0;
+}
+
 table.dataTable tbody > tr.selected, table.dataTable tbody > tr > .selected {
        background-color: #f5a55b !important;
 }