]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Restore wizard improvements
authorMarcin Haba <marcin.haba@bacula.pl>
Thu, 29 Apr 2021 19:10:25 +0000 (21:10 +0200)
committerMarcin Haba <marcin.haba@bacula.pl>
Thu, 29 Apr 2021 19:10:25 +0000 (21:10 +0200)
Changes:
 - Add search field
 - Add offset and limit fields
 - Count number of files and directories
 - Backup job information
 - Change in sorting - uppercase letters higher than lowercase letters

gui/baculum/protected/API/Class/BVFS.php
gui/baculum/protected/Web/Lang/en/messages.mo
gui/baculum/protected/Web/Lang/en/messages.po
gui/baculum/protected/Web/Pages/RestoreWizard.page
gui/baculum/protected/Web/Pages/RestoreWizard.php
gui/baculum/themes/Baculum-v2/css/restore-wizard.css

index a3fb5056452610e468161adf081dfd19539e6abc..f991b8acbef288fcd7f0393812f5c2a5c06e9ff4 100644 (file)
@@ -105,6 +105,6 @@ function sortFilesListByName($a, $b) {
        } else if ($firstRight == '.' && $firstLeft != '.') {
                return 1;
        }
-       return strcasecmp($a['name'], $b['name']);
+       return strcmp($a['name'], $b['name']);
 }
 ?>
index 44fddc761a45c0c6d48f688940a54dda3f7cbe97..6035a7cb262dd35cd7917a4532678caa89dc13ca 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 58ab65d90be9d55c2c39ba9bd53436af22190c3b..7b326e7f0b1f9d24ed12df9f0ada4dba7a1da4fb 100644 (file)
@@ -3334,3 +3334,24 @@ msgstr "SD port"
 
 msgid "Configure storage daemon"
 msgstr "Configure storage daemon"
+
+msgid "# Directories:"
+msgstr "# Directories:"
+
+msgid "# Files:"
+msgstr "# Files:"
+
+msgid "Search..."
+msgstr "Search..."
+
+msgid "case sensitive"
+msgstr "case sensitive"
+
+msgid "Backup client"
+msgstr "Backup client"
+
+msgid "Job type"
+msgstr "Job type"
+
+msgid "Restore time point"
+msgstr "Restore time point"
index 3883d005b72f01e73a98c5e58ba08df7ab450b5f..55ded96834dc5ab30e372e69e106cfba5322da10 100644 (file)
        </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'] . ');' : ''%>
+               <%=$this->Session->contains('restore_job') ? 'oJobsToRestoreList.set_jobid(' . $this->Session['restore_job']['jobid'] . ');' : ''%>
        </prop:ClientSide.OnComplete>
 </com:TCallback>
 <script type="text/javascript">
@@ -463,9 +463,9 @@ var oJobsToRestoreList = {
                                                        var radio = document.createElement('INPUT');
                                                        radio.type = 'radio';
                                                        radio.name = 'backup_to_restore';
-                                                       radio.value = data;
+                                                       radio.value = [data, row.name, row.type, row.endtime, row.jobstatus].join('|');
                                                        radio.className = 'w3-radio';
-                                                       if ('<%=$this->Session->contains('restore_jobid') ? $this->Session['restore_jobid'] : ''%>' == data) {
+                                                       if ('<%=$this->Session->contains('restore_job') ? $this->Session['restore_job']['jobid'] : ''%>' == data) {
                                                                radio.setAttribute('checked', 'checked');
                                                        }
                                                        ret = radio.outerHTML;
@@ -568,28 +568,78 @@ $(function() {
                        <com:TPanel ID="NoFileFound" Style="height: 38px" Display="None">
                                        <%[ No file found for selected backup. It can mean file records for this backup are pruned. Restoring selected files is not available but if you continue, there will be performed full restore all backup files. ]%>
                        </com:TPanel>
+                       <com:TCallback
+                               ID="LoadPath"
+                               OnCallback="loadPath"
+                       >
+                               <prop:ClientSide.OnLoading>
+                                       document.getElementById('restore-browser-files-loading').style.display = 'block';
+                               </prop:ClientSide.OnLoading>
+                               <prop:ClientSide.OnComplete>
+                                       document.getElementById('restore-browser-files-loading').style.display = 'none';
+                                       make_draggable('restore-browser-files-content');
+                               </prop:ClientSide.OnComplete>
+                       </com:TCallback>
                        <div class="w3-row">
-                               <div class="w3-col" style="width: 15%;"><%[ Path: ]%></div>
+                               <div class="w3-col" style="width: 15%; text-align: right; padding: 10px;"><%[ Path: ]%></div>
                                <div class="w3-col" style="width: 85%;">
                                        <com:TActiveTextBox
                                                ID="PathField"
                                                CssClass="w3-input w3-twothird w3-border"
                                                Style="display: inline"
-                                               Attributes.onkeydown="var keycode = (event.keyCode ? event.keyCode : event.which); if (keycode === 13) {$('#<%=$this->LoadPathBtn->ClientID%>').click(); return false; } return true;"
+                                               Attributes.onkeydown="var keycode = (event.keyCode ? event.keyCode : event.which); if (keycode === 13) {oRestoreBrowserFiles.load_browser_content(); return false; } return true;"
                                                />
-                                       <com:TActiveLinkButton
-                                               ID="LoadPathBtn"
-                                               CssClass="w3-button w3-green"
-                                               OnClick="loadPath">
-                                               <prop:ClientSide.OnLoading>
-                                                       document.getElementById('restore-browser-files-loading').style.display = 'block';
-                                               </prop:ClientSide.OnLoading>
-                                               <prop:ClientSide.OnComplete>
-                                                       document.getElementById('restore-browser-files-loading').style.display = 'none';
-                                                       make_draggable('restore-browser-files-content');
-                                               </prop:ClientSide.OnComplete>
+                                       <button type="button" id="load_path_btn" class="w3-button w3-green" onclick="oRestoreBrowserFiles.load_browser_content();">
                                                <i class="fa fa-check"></i> &nbsp;<%[ OK ]%>
-                                       </com:TActiveLinkButton>
+                                       </button>
+                               </div>
+                       </div>
+                       <div class="w3-row w3-hide-small w3-small">
+                               <span><%[ # Directories: ]%></span>
+                               <com:TActiveLabel
+                                       ID="RestoreBrowserDirCount"
+                                       CssClass="w3-show-inline-block"
+                                       Style="min-width: 20px;"
+                                       Text="0"
+                               />
+                               <span><%[ # Files: ]%></span>
+                               <com:TActiveLabel
+                                       ID="RestoreBrowserFileCount"
+                                       CssClass="w3-show-inline-block"
+                                       Style="min-width: 20px;"
+                                       Text="0"
+                               />
+                               <div class="w3-right w3-half w3-hide-small" style="text-align: right; margin-top: 5px">
+                                       <span><%[ Backup client ]%>:</span>
+                                       <com:TActiveLabel
+                                               ID="RestoreBrowserClient"
+                                               CssClass="w3-show-inline-block w3-margin-right"
+                                               Style="font-weight: bold"
+                                       />
+                                       <span><%[ Job type ]%>:</span>
+                                       <com:TActiveLabel
+                                               ID="RestoreBrowserType"
+                                               CssClass="w3-show-inline-block w3-margin-right"
+                                               Style="font-weight: bold"
+                                       />
+                                       <span><%[ Job name ]%>:</span>
+                                       <com:TActiveLabel
+                                               ID="RestoreBrowserName"
+                                               CssClass="w3-show-inline-block w3-margin-right"
+                                               Style="font-weight: bold"
+                                       />
+                                       <span><%[ Status ]%>: </span>
+                                       <com:TActiveLabel
+                                               ID="RestoreBrowserStatus"
+                                               CssClass="w3-show-inline-block w3-margin-right"
+                                               Style="vertical-align: top"
+                                       />
+                                       <span><%[ Restore time point ]%>:</span>
+                                       <com:TActiveLabel
+                                               ID="RestoreBrowserTimePoint"
+                                               CssClass="w3-show-inline-block"
+                                               Style="font-weight: bold"
+                                       />
                                </div>
                        </div>
                        <table id="restore-browser">
@@ -599,10 +649,38 @@ $(function() {
                                                <div id="restore-browser-files" class="w3-border">
                                                        <div id="restore-browser-files-content"></div>
                                                </div>
+                                               <div id="restore-browser-nav" class="w3-hide-small" style="height: 57px; padding: 8px 0;">
+                                                       <button type="button" class="w3-button w3-dark-grey w3-right" onclick="oRestoreBrowserFiles.next_page();"><%[ Next ]%> &nbsp;<i class="fa fa-arrow-right"></i></button>
+                                                       <button type="button" class="w3-button w3-dark-grey w3-right w3-margin-right" onclick="oRestoreBrowserFiles.prev_page();"><i class="fa fa-arrow-left"></i> &nbsp;<%[ Previous ]%></button>
+                                                       <com:TActiveTextBox
+                                                               ID="RestoreBrowserLimit"
+                                                               CssClass="w3-input w3-border w3-right w3-show-inline-block w3-margin-right"
+                                                               Width="70px"
+                                                               Text="2000"
+                                                       />
+                                                       <span class="w3-right" style="padding: 8px 2px 8px 8px"><%[ Limit: ]%></span>
+                                                       <com:TActiveTextBox
+                                                               ID="RestoreBrowserOffset"
+                                                               CssClass="w3-input w3-border w3-right w3-show-inline-block"
+                                                               Width="70px"
+                                                               Text="0"
+                                                       />
+                                                       <span class="w3-right" style="padding: 8px 2px"><%[ Offset: ]%></span>
+                                                       <input type="text" id="restore-browser-search" class="w3-input w3-border w3-show-inline-block" style="width: 170px" placeholder="&#128269; <%[ Search... ]%>" />
+                                                       <input type="checkbox" class="w3-check w3-margin-left" id="restore-browser-search-insensitive" />
+                                                       <label for="restore-browser-search-insensitive" style="padding: 8px 2px"><%[ case sensitive ]%></label>
+                                               </div>
 <script>
 var oRestoreBrowserFiles = {
        ids: {
-               content: 'restore-browser-files-content'
+               content: 'restore-browser-files-content',
+               search: 'restore-browser-search',
+               case_sensitive: 'restore-browser-search-insensitive',
+               offset: '<%=$this->RestoreBrowserOffset->ClientID%>',
+               limit: '<%=$this->RestoreBrowserLimit->ClientID%>',
+               type: '<%=$this->RestoreBrowserType->ClientID%>',
+               status: '<%=$this->RestoreBrowserStatus->ClientID%>',
+               time_point: '<%=$this->RestoreBrowserTimePoint->ClientID%>'
        },
        cls: {
                draggable: 'draggable',
@@ -627,7 +705,13 @@ var oRestoreBrowserFiles = {
        },
        excluded_items: ['.', '..'],
        init: function(items) {
+               this.set_events();
                this.load_items();
+               this.set_browser_headers();
+       },
+       load_browser_content: function() {
+               var cb = <%=$this->LoadPath->ActiveControl->Javascript%>;
+               cb.dispatch();
        },
        load_items: function() {
                var cb = <%=$this->SetBrowserFiles->ActiveControl->Javascript%>;
@@ -670,6 +754,12 @@ var oRestoreBrowserFiles = {
                        if(vposition == null || vposition == '0px'){
                                event.stop();
                        } else {
+                               if (item.type === this.types.directory) {
+                                       var offset = document.getElementById(this.ids.offset);
+                                       offset.value = 0;
+                                       var search = document.getElementById(this.ids.search);
+                                       search.value = '';
+                               }
                                var cbp = [item.name, item.pathid, item.filenameid, item.jobid].join('|');
                                var request = <%=$this->getPage()->GetVersions->ActiveControl->Javascript%>;
                                request.setCallbackParameter(cbp);
@@ -728,11 +818,75 @@ var oRestoreBrowserFiles = {
                div.appendChild(right);
                container.appendChild(div);
        },
+       set_events: function() {
+               document.getElementById(this.ids.offset).addEventListener('keypress', function(e) {
+                       var kc = event.which || event.keyCode;
+                       if (kc === 13) {
+                               this.load_browser_content();
+                       }
+               }.bind(this));
+               document.getElementById(this.ids.limit).addEventListener('keypress', function(e) {
+                       var kc = event.which || event.keyCode;
+                       if (kc === 13) {
+                               this.load_browser_content();
+                       }
+               }.bind(this));
+               var search = document.getElementById(this.ids.search);
+               var case_sensitive = document.getElementById(this.ids.case_sensitive);
+               var search_func = function(e) {
+                       var els = document.querySelectorAll('#' + this.ids.content + ' div.' + this.cls.item);
+                       var cs = case_sensitive.checked;
+                       var txt, regex, regex_opts;
+                       for (var i = 0; i < els.length; i++) {
+                               txt = els[i].childNodes[1].textContent;
+                               if (cs) {
+                                       regex = new RegExp(search.value);
+                               } else {
+                                       regex = new RegExp(search.value, 'i');
+                               }
+                               if (regex.test(txt)) {
+                                       if (els[i].style.display == 'none') {
+                                               els[i].style.removeProperty('display');
+                                       }
+                               } else {
+                                       els[i].style.display = 'none';
+                               }
+                       }
+               }.bind(this);
+               search.addEventListener('keyup', search_func);
+               case_sensitive.addEventListener('click', search_func);
+       },
+       prev_page: function() {
+               var offset = document.getElementById(this.ids.offset);
+               var limit = document.getElementById(this.ids.limit);
+               var new_offset = parseInt(offset.value, 10) - parseInt(limit.value, 10);
+               if (new_offset < 0) {
+                       new_offset = 0;
+               }
+               offset.value = new_offset;
+               this.load_browser_content();
+       },
+       next_page: function() {
+               var offset = document.getElementById(this.ids.offset);
+               var limit = document.getElementById(this.ids.limit);
+               var new_offset = parseInt(offset.value, 10) + parseInt(limit.value, 10);
+               offset.value = new_offset;
+               this.load_browser_content();
+       },
        clear_browser: function() {
                var container = document.getElementById(this.ids.content);
                while (container.firstChild) {
                        container.removeChild(container.firstChild);
                }
+       },
+       set_browser_headers: function() {
+               var type = document.getElementById(this.ids.type);
+               type.textContent = JobType.get_type(type.textContent);
+               var status = document.getElementById(this.ids.status);
+               status.innerHTML = JobStatus.get_icon(status.textContent).outerHTML;
+               var time_point = document.getElementById(this.ids.time_point);
+               var d = date_time_to_ts(time_point.textContent);
+               time_point.textContent = Units.format_date(d);
        }
 };
 $(function() {
index 5e0b81ecae177f28c8ca591de4fdc5aee638c50b..bf706713d7509733ec0a3e4a6172b3ccdae32e6f 100644 (file)
@@ -25,6 +25,7 @@ Prado::using('System.Exceptions.TException');
 Prado::using('System.Web.UI.WebControls.TWizard');
 Prado::using('System.Web.UI.WebControls.TDataGrid');
 Prado::using('System.Web.UI.JuiControls.TJuiDroppable');
+Prado::using('System.Web.UI.ActiveControls.TActiveLabel');
 Prado::using('System.Web.UI.ActiveControls.TActiveLinkButton');
 Prado::using('System.Web.UI.ActiveControls.TActiveDataGrid');
 Prado::using('System.Web.UI.ActiveControls.TActiveRepeater');
@@ -148,11 +149,11 @@ class RestoreWizard extends BaculumWebPage
         * @return none
         */
        public function setRestoreByJobId($jobid) {
-               $this->Session->add('restore_jobid', $jobid);
                $job = $this->getModule('api')->get(
                        ['jobs', $jobid]
                )->output;
                if (is_object($job)) {
+                       $this->setRestoreJob($jobid, $job->name, $job->type, $job->endtime, $job->jobstatus);
                        $this->loadRestoreClients();
                        $this->BackupClient->SelectedValue = $job->clientid;
                        $this->RestoreClient->SelectedValue = $job->clientid;
@@ -163,6 +164,19 @@ class RestoreWizard extends BaculumWebPage
                }
        }
 
+       private function setRestoreJob($jobid, $name, $type, $endtime, $jobstatus) {
+               $this->Session->add(
+                       'restore_job',
+                       [
+                               'jobid' => $jobid,
+                               'name' => $name,
+                               'type' => $type,
+                               'endtime' => $endtime,
+                               'jobstatus' => $jobstatus
+                       ]
+               );
+       }
+
        /**
         * Set navigation buttons.
         * Used for restore specific jobid (hide previous button)
@@ -193,14 +207,12 @@ class RestoreWizard extends BaculumWebPage
                        $this->loadRestoreClients();
                        if ($this->BackupClient->DataChanged) {
                                // remove previous restore jobid only if user changed client selection
-                               $this->Session->remove('restore_jobid');
+                               $this->Session->remove('restore_job');
                        }
                } elseif ($param->CurrentStepIndex === 1) {
                        if ($this->Request->contains('backup_to_restore')) {
-                               $this->Session->add(
-                                       'restore_jobid',
-                                       $this->Request['backup_to_restore']
-                               );
+                               list($jobid, $name, $type, $endtime, $jobstatus) = explode('|', $this->Request['backup_to_restore'], 5);
+                               $this->setRestoreJob($jobid, $name, $type, $endtime, $jobstatus);
                        }
                        $this->setRestorePath();
                        $this->setFileVersions();
@@ -470,9 +482,14 @@ class RestoreWizard extends BaculumWebPage
                        // generating Bvfs may take a moment
                        $this->generateBvfsCache($jobids);
 
+                       $offset = intval($this->RestoreBrowserOffset->Text);
+                       $limit = intval($this->RestoreBrowserLimit->Text);
+
                        // get directory and file list
                        $q = [
                                'jobids' => $jobids,
+                               'offset' => $offset,
+                               'limit' => $limit,
                                'output' => 'json'
                        ];
                        if ($this->Session->contains('restore_pathid'))  {
@@ -489,6 +506,22 @@ class RestoreWizard extends BaculumWebPage
                        if ($bvfs_dirs->error === 0) {
                                $dirs = json_decode(json_encode($bvfs_dirs->output), true);
                        }
+                       $dir_count = count($dirs);
+                       if ($dir_count == 1 && ($dirs[0]['name'] == '/' || preg_match('/^[A-Z]+:\/$/i', $dirs[0]['name']) === 1)) {
+                               $this->RestoreBrowserDirCount->Text = $dir_count;
+                       } elseif ($dir_count == 0) {
+                               $this->RestoreBrowserDirCount->Text = 0;
+                       } else {
+                               $this->RestoreBrowserDirCount->Text = ($dir_count - 1);
+                       }
+
+                       if ($this->Session->contains('restore_job')) {
+                               $this->RestoreBrowserClient->Text = $this->BackupClient->SelectedItem->Text;
+                               $this->RestoreBrowserName->Text = $this->Session['restore_job']['name'];
+                               $this->RestoreBrowserType->Text = $this->Session['restore_job']['type'];
+                               $this->RestoreBrowserStatus->Text = $this->Session['restore_job']['jobstatus'];
+                               $this->RestoreBrowserTimePoint->Text = $this->Session['restore_job']['endtime'];
+                       }
 
                        // get files list
                        $bvfs_files = $this->getModule('api')->get(
@@ -498,6 +531,7 @@ class RestoreWizard extends BaculumWebPage
                        if ($bvfs_files->error === 0) {
                                $files = json_decode(json_encode($bvfs_files->output), true);
                        }
+                       $this->RestoreBrowserFileCount->Text = count($files);
 
                        $elements = array_merge($dirs, $files);
                        $elements = array_map(['RestoreWizard', 'addUniqid'], $elements);
@@ -507,7 +541,7 @@ class RestoreWizard extends BaculumWebPage
                }
                if (count($elements) > 0) {
                        $this->NoFileFound->Display = 'None';
-               } elseif ($this->Session->contains('restore_jobid')) {
+               } elseif ($this->Session->contains('restore_job')) {
                        $this->NoFileFound->Display = 'Dynamic';
                }
                $this->setBrowserFiles($elements);
@@ -539,9 +573,9 @@ class RestoreWizard extends BaculumWebPage
         */
        private function getElementaryBackup() {
                $jobids = '';
-               if ($this->OnlySelectedBackupSelection->Checked && $this->Session->contains('restore_jobid')) {
+               if ($this->OnlySelectedBackupSelection->Checked && $this->Session->contains('restore_job')) {
                        $params = [
-                               'jobid' => $this->Session['restore_jobid']
+                               'jobid' => $this->Session['restore_job']['jobid']
                        ];
                        if ($this->EnableCopyJobRestore->Checked) {
                                $params['inc_copy_job'] = 1;
@@ -558,7 +592,7 @@ class RestoreWizard extends BaculumWebPage
                                }
                        }
                        if (empty($jobids)) {
-                               $jobids = $this->Session['restore_jobid'];
+                               $jobids = $this->Session['restore_job']['jobid'];
                        }
                } else {
                        $params = [
@@ -577,6 +611,15 @@ class RestoreWizard extends BaculumWebPage
                        ]);
                        if (count($jobs_recent->output) > 0) {
                                $ids = $jobs_recent->output;
+                               if (count($ids) > 0) {
+                                       $jobid = $ids[0];
+                                       $job = $this->getModule('api')->get(
+                                               ['jobs', $jobid]
+                                       )->output;
+                                       if (is_object($job)) {
+                                               $this->setRestoreJob($jobid, $job->name, $job->type, $job->endtime, $job->jobstatus);
+                                       }
+                               }
                                $jobids = implode(',', $ids);
                        }
                }
@@ -601,6 +644,7 @@ class RestoreWizard extends BaculumWebPage
                        }
                        $path[$i] .= '/';
                }
+               $path = array_filter($path); // remove empty item if any
                $this->goToPath($path, true);
        }
 
@@ -609,7 +653,7 @@ class RestoreWizard extends BaculumWebPage
         * There is possible to pass both single directory 'somedir'
         * or whole path '/etc/somedir'.
         *
-        * @param string $path path to go
+        * @param string|array $path path to go
         * @param bool $full_path determines if $path param is full path or relative path (singel directory)
         * @return none
         */
@@ -1013,11 +1057,11 @@ class RestoreWizard extends BaculumWebPage
                        $jobid = $this->getModule('misc')->findJobIdStartedJob($ret->output);
                        // Remove temporary BVFS table
                        $this->getModule('api')->set(['bvfs', 'cleanup'], ['path' => $path]);
-               } elseif (count($this->Session['files_browser']) === 0 && $this->Session->contains('restore_jobid')) {
+               } elseif (count($this->Session['files_browser']) === 0 && $this->Session->contains('restore_job')) {
                        $restore_props['full'] = 1;
-                       $restore_props['id'] = $this->Session['restore_jobid'];
+                       $restore_props['id'] = $this->Session['restore_job']['jobid'];
                        $job = $this->getModule('api')->get(
-                               ['jobs', $this->Session['restore_jobid']]
+                               ['jobs', $this->Session['restore_job']['jobid']]
                        )->output;
                        if (is_object($job)) {
                                $restore_props['fileset'] = $job->fileset;
@@ -1103,7 +1147,7 @@ class RestoreWizard extends BaculumWebPage
                $this->loadRestoreJobs();
                $this->Session->remove('restore_path');
                $this->Session->remove('restore_pathid');
-               $this->Session->remove('restore_jobid');
+               $this->Session->remove('restore_job');
                $this->Session->remove('file_relocation');
        }
 }
index c298e7c04c11dbceda3f86879c6be6c7cff61987..2412b465b2ba9b3e64dcdd53c7abe7f2c5a84b0e 100644 (file)
        background-color: white;
 }
 
+#restore-browser-files {
+       height: calc(100% - 57px) !important;
+}
+
+@media (max-width: 600px) {
+       #restore-browser-files {
+               /* On small devices bottom toolbar with search, offset and limit is not displayed */
+               height: 100% !important;
+       }
+}
+
 #restore-browser-files-loading {
        position: absolute;
        z-index: 10;