]> git.sur5r.net Git - bacula/bacula/commitdiff
baculum: New reworked restore wizard
authorMarcin Haba <marcin.haba@bacula.pl>
Mon, 27 Nov 2017 20:08:47 +0000 (21:08 +0100)
committerKern Sibbald <kern@sibbald.com>
Sat, 9 Dec 2017 11:01:07 +0000 (12:01 +0100)
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/Pages/RestoreWizard.page
gui/baculum/protected/Web/Pages/RestoreWizard.php
gui/baculum/themes/Baculum-v1/restore-wizard.css [new file with mode: 0644]

index 523418c02ef99a531ac27da154c1ed2774a0984d..946847662181ac818b996c17453b58a0f08a6b5b 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 b9d0409fd138a32a774f8806aae093e6cedcba91..c0c29fd9e1971f28f248770b5aa5918a7ed995e6 100644 (file)
@@ -1436,3 +1436,68 @@ msgstr "Resource %s \"%s\" removed successfully."
 msgid "Please unassign resource %s \"%s\" from these resources and try again."
 msgstr "Please unassign resource %s \"%s\" from these resources and try again."
 
+msgid "Size:"
+msgstr "Size:"
+
+msgid "JobId:"
+msgstr "JobId:"
+
+msgid "Volume:"
+msgstr "Volume:"
+
+msgid "InChanger:"
+msgstr "InChanger:"
+
+msgid "Sum:"
+msgstr "Sum:"
+
+msgid "Select"
+msgstr "Select"
+
+msgid "Path:"
+msgstr "Path:"
+
+msgid "Restore wizard"
+msgstr "Restore wizard"
+
+msgid "This wizard enables you to do in easy way restore files to destination Bacula Client."
+msgstr "This wizard enables you to do in easy way restore files to destination Bacula Client."
+
+msgid "To start, please select backup Client which data you want to restore."
+msgstr "To start, please select backup Client which data you want to restore."
+
+msgid "There are two ways to select backup to restore. Only selected backup way provides list of jobs from which there is possible to select one job. If you need the latest backups from the Client alternatively you can use Group most recent backups way which will select backups for you basing on backup job name and fileset resource."
+msgstr "There are two ways to select backup to restore. Only selected backup way provides list of jobs from which there is possible to select one job. If you need the latest backups from the Client alternatively you can use Group most recent backups way which will select backups for you basing on backup job name and fileset resource."
+
+msgid "Do not relocate files"
+msgstr "Do not relocate files"
+
+msgid "Relocate files with prefix and/or suffix"
+msgstr "Relocate files with prefix and/or suffix"
+
+msgid "Relocate files with regular expression"
+msgstr "Relocate files with regular expression"
+
+msgid "Strip prefix:"
+msgstr "Strip prefix:"
+
+msgid "Add prefix:"
+msgstr "Add prefix:"
+
+msgid "Add suffix:"
+msgstr "Add suffix:"
+
+msgid "RegexWhere:"
+msgstr "RegexWhere:"
+
+msgid "File relocation option:"
+msgstr "File relocation option:"
+
+msgid "In Changer"
+msgstr "In Changer"
+
+msgid "Volume"
+msgstr "Volume"
+
+msgid "During restore there will be used following volumes:"
+msgstr "During restore there will be used following volumes:"
index 4454027436636d6b80b82d087c1dc87cb62b4d91..1f7b5968773bf6d9f0507d7e18288969fcd254ee 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 1d460895ec7d5d7533cc4283bee44bfdbf2e7259..6ed67fd1b9828cf24465c6174f263a739caaf5e6 100644 (file)
@@ -1187,3 +1187,68 @@ msgstr "Resource %s \"%s\" removed successfully."
 msgid "Please unassign resource %s \"%s\" from these resources and try again."
 msgstr "Please unassign resource %s \"%s\" from these resources and try again."
 
+msgid "Size:"
+msgstr "Size:"
+
+msgid "JobId:"
+msgstr "JobId:"
+
+msgid "Volume:"
+msgstr "Volume:"
+
+msgid "InChanger:"
+msgstr "InChanger:"
+
+msgid "Sum:"
+msgstr "Sum:"
+
+msgid "Select"
+msgstr "Select"
+
+msgid "Path:"
+msgstr "Path:"
+
+msgid "Restore wizard"
+msgstr "Restore wizard"
+
+msgid "This wizard enables you to do in easy way restore files to destination Bacula Client."
+msgstr "This wizard enables you to do in easy way restore files to destination Bacula Client."
+
+msgid "To start, please select backup Client which data you want to restore."
+msgstr "To start, please select backup Client which data you want to restore."
+
+msgid "There are two ways to select backup to restore. Only selected backup way provides list of jobs from which there is possible to select one job. If you need the latest backups from the Client alternatively you can use Group most recent backups way which will select backups for you basing on backup job name and fileset resource."
+msgstr "There are two ways to select backup to restore. Only selected backup way provides list of jobs from which there is possible to select one job. If you need the latest backups from the Client alternatively you can use Group most recent backups way which will select backups for you basing on backup job name and fileset resource."
+
+msgid "Do not relocate files"
+msgstr "Do not relocate files"
+
+msgid "Relocate files with prefix and/or suffix"
+msgstr "Relocate files with prefix and/or suffix"
+
+msgid "Relocate files with regular expression"
+msgstr "Relocate files with regular expression"
+
+msgid "Strip prefix:"
+msgstr "Strip prefix:"
+
+msgid "Add prefix:"
+msgstr "Add prefix:"
+
+msgid "Add suffix:"
+msgstr "Add suffix:"
+
+msgid "RegexWhere:"
+msgstr "RegexWhere:"
+
+msgid "File relocation option:"
+msgstr "File relocation option:"
+
+msgid "In Changer"
+msgstr "In Changer"
+
+msgid "Volume"
+msgstr "Volume"
+
+msgid "During restore there will be used following volumes:"
+msgstr "During restore there will be used following volumes:"
index 054235dd35a0f55c5893b8e6a042a2b09d113089..e37cc42c5999332de62465c088c219cbdb496bcc 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 9c5eae621ff6bc3187863db09851c2ad3d33c954..8dcc96ca93464e298063e8e444df0e1ce87a39e3 100644 (file)
@@ -1443,3 +1443,68 @@ msgstr "Zasób %s \"%s\" został usunięty pomyślnie."
 msgid "Please unassign resource %s \"%s\" from these resources and try again."
 msgstr "Proszę wypisać zasób %s \"%s\" z tych zasobów i spróbować ponownie."
 
+msgid "Size:"
+msgstr "Rozmiar:"
+
+msgid "JobId:"
+msgstr "JobId:"
+
+msgid "Volume:"
+msgstr "Wolumen:"
+
+msgid "InChanger:"
+msgstr "InChanger:"
+
+msgid "Sum:"
+msgstr "Suma:"
+
+msgid "Select"
+msgstr "Wybierz"
+
+msgid "Path:"
+msgstr "Lokalizacja:"
+
+msgid "Restore wizard"
+msgstr "Konfigurator przywracania danych"
+
+msgid "This wizard enables you to do in easy way restore files to destination Bacula Client."
+msgstr "Ten konfigurator umożliwia wykonanie w łatwy sposób przywracania plików do docelowego Klienta Bacula."
+
+msgid "To start, please select backup Client which data you want to restore."
+msgstr "Aby zacząć, proszę wybrać Klienta kopii zapasowych, którego dane chcesz przywrócić."
+
+msgid "There are two ways to select backup to restore. Only selected backup way provides list of jobs from which there is possible to select one job. If you need the latest backups from the Client alternatively you can use Group most recent backups way which will select backups for you basing on backup job name and fileset resource."
+msgstr "Są dwa sposoby na wybranie kopii zapasowych do przywrócenia. Sposób 'Tylko wybrany backup' dostarcza listę zadań, z których możliwe jest wybranie jednej kopii zapasowej. Jeśli potrzebujesz przywrócić dane z najnowszych backupów alternatywnie możesz użyć opcję 'Grupa najaktualniejszych backupów', która będzie wybierać backupy za ciebie bazując na zadaniu kopii zapasowych i zasobie fileset."
+
+msgid "Do not relocate files"
+msgstr "Nie relokuj plików"
+
+msgid "Relocate files with prefix and/or suffix"
+msgstr "Relokuj pliki z prefiksem i/lub sufiksem."
+
+msgid "Relocate files with regular expression"
+msgstr "Relokuj pliki z użyciem wyrażenia regularnego"
+
+msgid "Strip prefix:"
+msgstr "Obetnij prefiks:"
+
+msgid "Add prefix:"
+msgstr "Dodaj prefiks:"
+
+msgid "Add suffix:"
+msgstr "Dodaj sufiks:"
+
+msgid "RegexWhere:"
+msgstr "RegexWhere:"
+
+msgid "File relocation option:"
+msgstr "Opcja relokacji plików:"
+
+msgid "In Changer"
+msgstr "In Changer"
+
+msgid "Volume"
+msgstr "Wolumen"
+
+msgid "During restore there will be used following volumes:"
+msgstr "Podczas zadania przywracania danych potrzebne będą następujące wolumeny:"
index a4e3dcdf01408070ce66cf6afe20c57b274406c1..efb09de1e6095fd02efb56e327a3e6b09312ed36 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 9cbacbd9ae73cb5d820b134b914f2ab66067ab82..d2fd0176949fa72b967e044fb524698d73582092 100644 (file)
@@ -1451,3 +1451,68 @@ msgstr "Resource %s \"%s\" removed successfully."
 msgid "Please unassign resource %s \"%s\" from these resources and try again."
 msgstr "Please unassign resource %s \"%s\" from these resources and try again."
 
+msgid "Size:"
+msgstr "Size:"
+
+msgid "JobId:"
+msgstr "JobId:"
+
+msgid "Volume:"
+msgstr "Volume:"
+
+msgid "InChanger:"
+msgstr "InChanger:"
+
+msgid "Sum:"
+msgstr "Sum:"
+
+msgid "Select"
+msgstr "Select"
+
+msgid "Path:"
+msgstr "Path:"
+
+msgid "Restore wizard"
+msgstr "Restore wizard"
+
+msgid "This wizard enables you to do in easy way restore files to destination Bacula Client."
+msgstr "This wizard enables you to do in easy way restore files to destination Bacula Client."
+
+msgid "To start, please select backup Client which data you want to restore."
+msgstr "To start, please select backup Client which data you want to restore."
+
+msgid "There are two ways to select backup to restore. Only selected backup way provides list of jobs from which there is possible to select one job. If you need the latest backups from the Client alternatively you can use Group most recent backups way which will select backups for you basing on backup job name and fileset resource."
+msgstr "There are two ways to select backup to restore. Only selected backup way provides list of jobs from which there is possible to select one job. If you need the latest backups from the Client alternatively you can use Group most recent backups way which will select backups for you basing on backup job name and fileset resource."
+
+msgid "Do not relocate files"
+msgstr "Do not relocate files"
+
+msgid "Relocate files with prefix and/or suffix"
+msgstr "Relocate files with prefix and/or suffix"
+
+msgid "Relocate files with regular expression"
+msgstr "Relocate files with regular expression"
+
+msgid "Strip prefix:"
+msgstr "Strip prefix:"
+
+msgid "Add prefix:"
+msgstr "Add prefix:"
+
+msgid "Add suffix:"
+msgstr "Add suffix:"
+
+msgid "RegexWhere:"
+msgstr "RegexWhere:"
+
+msgid "File relocation option:"
+msgstr "File relocation option:"
+
+msgid "In Changer"
+msgstr "In Changer"
+
+msgid "Volume"
+msgstr "Volume"
+
+msgid "During restore there will be used following volumes:"
+msgstr "During restore there will be used following volumes:"
index cb4c85f2d40989dcfe959473283d14d2feffe123..76d4dc25501be981efdc21143c0c18ff31c62063 100644 (file)
@@ -3,10 +3,12 @@
        <com:TWizard ID="RestoreWizard"
                CssClass="wizard"
                StepStyle.CssClass="steps"
-               HeaderStyle.CssClass="header"
+               HeaderStyle.CssClass="wizard-body"
                NavigationStyle.CssClass="navigation"
                UseDefaultLayout="false"
                ShowSideBar="false"
+               OnPreviousButtonClick="wizardPrev"
+               OnNextButtonClick="wizardNext"
                OnCancelButtonClick="wizardStop"
                OnCompleteButtonClick="wizardCompleted"
                >
@@ -42,7 +44,7 @@
                        <div class="button-cancel"><com:BButton CommandName="Cancel" Text="<%[ Cancel ]%>" /></div>
                        <div class="button-prev-next">
                                <com:BButton CausesValidation="False" CssClass="bbutton" CommandName="PreviousStep" Text="<%[ &laquo; Previous ]%>" />
-                               <com:BButton ID="NextButton" CommandName="NextStep" Text="<%[ Next &raquo; ]%>" Visible="<%=($this->getPage()->RestoreWizard->ActiveStepIndex != 1 || ($this->getPage()->RestoreWizard->ActiveStepIndex == 1 && $this->getPage()->BackupToRestore->ItemCount > 0))%>" />
+                               <com:BButton ID="NextButton" CommandName="NextStep" Text="<%[ Next &raquo; ]%>" Visible="<%=($this->getPage()->RestoreWizard->ActiveStepIndex != 1 || ($this->getPage()->RestoreWizard->ActiveStepIndex == 1 && $this->getPage()->BackupsToRestore->ItemCount > 0))%>" />
                        </div>
                </prop:StepNavigationTemplate>
                 
                        </div>
                </prop:FinishNavigationTemplate>
                <com:TWizardStep ID="Step1" Title="<%[ Step 1 - select source backup client ]%>" StepType="Auto">
+                       <h2><%[ Restore wizard ]%></h2>
+                       <p>
+                               <%[ This wizard enables you to do in easy way restore files to destination Bacula Client. ]%>
+                               <%[ To start, please select backup Client which data you want to restore. ]%>
+                       </p>
                        <div class="line">
                                <div class="text"><com:TLabel ForControl="BackupClientName" Text="<%[ Backup from client: ]%>" /></div>
                                <div class="field">
-                                       <com:TActiveDropDownList ID="BackupClientName" CssClass="textbox" Width="350px" OnLoad="setBackupClients" OnTextChanged="setJobs" CausesValidation="false" />
-                                       <com:TCompareValidator CssClass="validator" Display="Dynamic" ControlToValidate="BackupClientName" DataType="String" ValueToCompare="none" Operator="NotEqual" Text="<%[ Please select language. ]%>" />
+                                       <com:TDropDownList ID="BackupClientName" CssClass="textbox" Width="350px" CausesValidation="false" />
                                </div>
                        </div>
                </com:TWizardStep>
                <com:TWizardStep ID="Step2" Title="<%[ Step 2 - select backup to restore ]%>" StepType="Auto">
-                       <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="validation-error-summary" Visible="<%=($this->BackupToRestore->ItemCount == 0 && $this->GroupBackupToRestore->ItemCount == 0)%>"/>
+                       <p>
+                               <%[ There are two ways to select backup to restore. Only selected backup way provides list of jobs from which there is possible to select one job. If you need the latest backups from the Client alternatively you can use Group most recent backups way which will select backups for you basing on backup job name and fileset resource. ]%>
+                       </p>
                        <div class="line" style="margin-top: 15px">
                                <div class="text"><com:TLabel Text="<%[ Backup selection method: ]%>" /></div>
                                <div class="field">
-                                       <com:TActiveRadioButton ID="OnlySelectedBackupSelection" GroupName="BackupSelection" Checked="true" OnCheckedChanged="setBackupSelection" /> <com:TLabel ForControl="OnlySelectedBackupSelection" Text="<%[ Only selected backup ]%>" />
-                                       <com:TActiveRadioButton ID="GroupBackupSelection" GroupName="BackupSelection" OnCheckedChanged="setBackupSelection" /> <com:TLabel ForControl="GroupBackupSelection" Text="<%[ Group most recent backups ]%>" /></div>
+                                       <com:TRadioButton
+                                               ID="OnlySelectedBackupSelection"
+                                               GroupName="BackupSelection"
+                                               Checked="true"
+                                               Attributes.onclick="$('#group-backup-to-restore-field').hide(); $('#backup-to-restore-field').show();"
+                                       />
+                                       <com:TLabel ForControl="OnlySelectedBackupSelection" Text="<%[ Only selected backup ]%>" />
+                                       <com:TRadioButton
+                                               ID="GroupBackupSelection"
+                                               GroupName="BackupSelection"
+                                               Attributes.onclick="$('#backup-to-restore-field').hide(); $('#group-backup-to-restore-field').show();"
+                                       />
+                                       <com:TLabel ForControl="GroupBackupSelection" Text="<%[ Group most recent backups ]%>" /></div>
                        </div>
-                       <com:TActivePanel ID="BackupToRestoreField">
-                               <div class="line">
-                                       <div class="text"><com:TLabel ForControl="BackupToRestore" Text="<%[ Backup for restore: ]%>" /></div>
-                                       <div class="field">
-                                               <com:TActiveDropDownList ID="BackupToRestore" CssClass="textbox" Width="350px" CausesValidation="false" OnTextChanged="resetFileBrowser" />
-                                       </div>
+                       <div id="backup-to-restore-field" style="display: <%=!$this->OnlySelectedBackupSelection->Checked ? 'none' : 'block'%>">
+                               <div>
+                               <p><%[ Backup for restore: ]%></p>
+                               <com:TDataGrid
+                                       ID="BackupsToRestore"
+                                       EnableViewState="false"
+                                       AutoGenerateColumns="false"
+                                       AllowSorting="false"
+                                       CellPadding="5px"
+                                       CssClass="window-section-detail"
+                                       ItemStyle.CssClass="slide-window-element"
+                                       AlternatingItemStyle.CssClass="slide-window-element-alternating"
+                                       >
+                                       <com:TBoundColumn HeaderText="ID" DataField="jobid" />
+                                       <com:TTemplateColumn HeaderText="<%[ Job name ]%>" SortExpression="name">
+                                               <prop:ItemTemplate>
+                                                       <div title="<%=$this->getParent()->Data['name']%>"><%=$this->getParent()->Data['name']%></div>
+                                               </prop:ItemTemplate>
+                                       </com:TTemplateColumn>
+                                       <com:TTemplateColumn>
+                                               <prop:HeaderText>
+                                                       <span title="<%=Prado::localize('Type')%>" style="cursor: help">T</span>
+                                               </prop:HeaderText>
+                                               <prop:ItemTemplate>
+                                                       <%=$this->getParent()->Data['type']%>
+                                               </prop:ItemTemplate>
+                                       </com:TTemplateColumn>
+                                       <com:TTemplateColumn>
+                                               <prop:HeaderText>
+                                                       <span title="<%=Prado::localize('Level')%>" style="cursor: help">L</span>
+                                               </prop:HeaderText>
+                                               <prop:ItemTemplate>
+                                                       <%=$this->getParent()->Data['level']%>
+                                               </prop:ItemTemplate>
+                                       </com:TTemplateColumn>
+                                       <com:TTemplateColumn HeaderText="<%[ Job status ]%>" SortExpression="jobstatus">
+                                               <prop:ItemTemplate>
+                                                       <div class="job-status-<%=$this->getParent()->Data['jobstatus'] == 'T' && $this->getParent()->Data['joberrors'] > 0 ? 'W' : $this->getParent()->Data['jobstatus']%>" title="<%=$this->getPage()->getModule('misc')->jobStates[$this->getParent()->Data['jobstatus'] == 'T' && $this->getParent()->Data['joberrors'] > 0 ? 'W' : $this->getParent()->Data['jobstatus']]['description']%>"><%=$this->getPage()->getModule('misc')->jobStates[$this->getParent()->Data['jobstatus']]['value']%></div>
+                                               </prop:ItemTemplate>
+                                       </com:TTemplateColumn>
+                                       <com:TTemplateColumn HeaderText="<%[ Size ]%>" SortExpression="jobbytes">
+                                               <prop:ItemTemplate>
+                                                       <div class="size" rel="<%=$this->getParent()->Data['jobbytes']%>"><%=$this->getParent()->Data['jobbytes']%></div>
+                                               </prop:ItemTemplate>
+                                       </com:TTemplateColumn>
+                                       <com:TBoundColumn SortExpression="jobfiles" HeaderText="<%[ Files ]%>" DataField="jobfiles" />
+                                       <com:TTemplateColumn HeaderText="<%[ End time ]%>" SortExpression="endtime">
+                                               <prop:ItemTemplate>
+                                                       <%=$this->getParent()->Data['endtime']%>
+                                               </prop:ItemTemplate>
+                                       </com:TTemplateColumn>
+                                       <com:TTemplateColumn HeaderText="<%[ Select ]%>">
+                                               <prop:ItemTemplate>
+                                                       <div class="center"><input type="radio" name="backup_to_restore" value="<%=$this->getParent()->Data['jobid']%>" /></div>
+                                               </prop:ItemTemplate>
+                                       </com:TTemplateColumn>
+                               </com:TDataGrid>
+                               <script type="text/javascript">
+                                       var is_list = <%=$this->BackupsToRestore->ItemCount%> > 0;
+                                       if (is_list) {
+                                               SlideWindow.makeSortable('<%=$this->BackupsToRestore->ClientID%>');
+                                               SlideWindow.sortTable('<%=$this->BackupsToRestore->ClientID%>', 0, true);
+                                               Formatters.set_formatters();
+                                               var grid_id = '<%=$this->BackupsToRestore->ClientID%>';
+                                               var restore_single_jobid = '<%=$this->restore_single_jobid%>';
+                                               var rows = (
+                                                       '#' + grid_id + ' tr.' + SlideWindow.elements.contentItems + ', ' +
+                                                       '#' + grid_id + ' tr.' + SlideWindow.elements.contentAlternatingItems
+                                               );
+                                               $(rows).each(function(index, el) {
+                                                       $(el).on('click', function(e) {
+                                                               var el = $(e.srcElement||e.target);
+                                                               if (el.length === 1) {
+                                                                       el = el.parents('tr').find('input[type=radio]');
+                                                                       if (el.length === 1) {
+                                                                               el[0].checked = true;
+                                                                       }
+                                                               }
+                                                       });
+                                                       var radio = $(el).find('input[type=radio]')[0];
+                                                       if (restore_single_jobid === radio.value) {
+                                                               radio.checked = true;
+                                                       }
+                                               }.bind(this));
+                                       } else {
+                                               document.getElementById('backup-to-restore-field').style.display = 'none';
+                                       }
+                               </script>
                                </div>
-                       </com:TActivePanel>
-                       <com:TActivePanel ID="GroupBackupToRestoreField" Display="None">
+                       </div>
+                       <div id="group-backup-to-restore-field" style="display: <%=!$this->GroupBackupSelection->Checked ? 'none' : 'block'%>">
                                <div class="line">
                                        <div class="text"><com:TLabel ForControl="GroupBackupToRestore" Text="<%[ Backup for restore: ]%>" /></div>
                                        <div class="field">
-                                               <com:TActiveDropDownList ID="GroupBackupToRestore" CssClass="textbox" Width="350px" CausesValidation="false" />
+                                               <com:TDropDownList ID="GroupBackupToRestore" CssClass="textbox" Width="350px" CausesValidation="false" />
                                        </div>
                                </div>
                                <div class="line">
                                        <div class="text"><com:TLabel ForControl="GroupBackupFileSet" Text="<%[ FileSet resource: ]%>" /></div>
                                        <div class="field">
-                                               <com:TActiveDropDownList ID="GroupBackupFileSet" CssClass="textbox" Width="350px" CausesValidation="false" />
+                                               <com:TDropDownList ID="GroupBackupFileSet" CssClass="textbox" Width="350px" CausesValidation="false" />
                                        </div>
                                </div>
-                       </com:TActivePanel>
+                       </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="validation-error-summary" Visible="<%=($this->BackupsToRestore->ItemCount == 0 && $this->GroupBackupToRestore->ItemCount == 0)%>"/>
+
                </com:TWizardStep>
                <com:TWizardStep ID="Step3" Title="<%[ Step 3 - select files to restore ]%>" StepType="Auto">
+                       <%[ Path: ]%> <com:TActiveTextBox
+                                       ID="PathField"
+                                       CssClass="textbox-auto"
+                                       Attributes.onkeydown="var keycode = (event.keyCode ? event.keyCode : event.which); if (keycode === 13) {$('<%=$this->LoadPathBtn->ClientID%>').click(); return false; } return true;"
+                                       Width="895px" />
+                       <com:TActiveLinkButton ID="LoadPathBtn" OnClick="loadPath" Text="<%[ OK ]%>">
+                               <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';
+                               </prop:ClientSide.OnComplete>
+                       </com:TActiveLinkButton>
                        <table id="restore-browser">
                                <tr>
                                        <td rowspan="2">
                                                <div id="restore-browser-files-loading" style="display: none"></div>
                                                <div id="restore-browser-files">
-                                                       <com:TActiveDataGrid
-                                                               ID="DataGridFiles"
-                                                               AutoGenerateColumns="false"
-                                                               CellPadding="2px"
-                                                               HeaderStyle.CssClass="file-browser-header"
-                                                               CssClass="file-browser-detail"
-                                                               ItemStyle.CssClass="file-browser-element"
-                                                               DataKeyField="fileid">
-                                                                       <prop:EmptyTemplate>
-                                                                               <div class="file-browser-watermark" style="padding: 150px 10px">
-                                                                                       <%[ It seems that there is no files for choosing or file records in database for this job has been purged (file retention period expired) ]%>
+                                               <com:TActiveDataGrid
+                                                       ID="DataGridFiles"
+                                                       AutoGenerateColumns="false"
+                                                       CellPadding="2px"
+                                                       ShowHeader="false"
+                                                       CssClass="file-browser-detail"
+                                                       ItemStyle.CssClass="file-browser-element"
+                                                       DataKeyField="fileid">
+                                                               <prop:EmptyTemplate>
+                                                                       <div class="file-browser-watermark" style="padding: 200px 10px">
+                                                                               <%[ It seems that there is no files for choosing or file records in database for this job has been purged (file retention period expired) ]%>
+                                                                       </div>
+                                                               </prop:EmptyTemplate>
+                                                               <com:TActiveTemplateColumn>
+                                                                       <prop:ItemTemplate>
+                                                                               <com:TDraggable ID="FileElementLeft" Revert="false" Handle="<%=$this->FileElementLeft->ClientID%>" Ghosting="true" CssClass="draggable">
+                                                                                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/<%=($this->getParent()->Data['type'] == 'dir' ? 'directory-icon' : 'file-icon')%>.png" alt="<%=$this->getParent()->Data['type'] == 'dir' ? 'directory' : 'file' %>" /> <%=($this->getParent()->Data['name'] != '/') ? preg_replace('/\/$/', '', $this->getParent()->Data['name']) : '/'%>
+                                                                               <div class="restore-browser-element-size">
+                                                                                       <com:TActiveLinkButton
+                                                                                               CssClass="link"
+                                                                                               Style="float: right;"
+                                                                                               OnCallback="Page.addFileToRestore"
+                                                                                               ActiveControl.CallbackParameter="<%=isset($this->getParent()->Data['fileid']) ? $this->getParent()->Data['fileid'] : null%>"
+                                                                                               ActiveControl.EnableUpdate="false"
+                                                                                               Visible="<%=!in_array($this->getParent()->Data['name'], $this->getPage()->excluded_elements_from_add)%>"
+                                                                                               Text="<%[ Add ]%>"
+                                                                                       />
+                                                                                       <span class="size" style=""><%=isset($this->getParent()->Data['lstat']['size']) ? $this->getParent()->Data['lstat']['size'] : '0'%></span>
                                                                                </div>
-                                                                       </prop:EmptyTemplate>
-                                                                       <com:TActiveTemplateColumn ID="PathField">
-                                                                               <prop:ItemTemplate>
-                                                                                       <com:TDraggable ID="FileElementLeft" Revert="false" Handle="<%=$this->FileElementLeft->ClientID%>" Ghosting="true" CssClass="draggable">
-                                                                                               <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/<%=($this->getParent()->Data['type'] == 'dir' ? 'directory-icon' : 'file-icon')%>.png" alt="<%=$this->getParent()->Data['type'] == 'dir' ? 'directory' : 'file' %>" /> <%=($this->getParent()->Data['name'] != '/') ? preg_replace('/\/$/', '', $this->getParent()->Data['name']) : '/'%>
-                                                                                                               <com:TActiveLinkButton CssClass="link" Style="float: right;" OnCallback="Page.addFileToRestore" ActiveControl.CallbackParameter="<%=isset($this->getParent()->Data['fileid']) ? $this->getParent()->Data['fileid'] : null%>" ActiveControl.EnableUpdate="false" Visible="<%=!in_array($this->getParent()->Data['name'], $this->getPage()->excluded_elements_from_add)%>" Text="<%[ Add ]%>" />
-                                                                                       </com:TDraggable>
-                                                                                       <com:TCallback ID="FileElementCall" OnCallback="Page.getVersions" ActiveControl.CallbackParameter="<%=$this->getParent()->Data['name'] . '|' . @$this->getParent()->Data['pathid'] . '|' . @$this->getParent()->Data['filenameid'] . '|' . @$this->getParent()->Data['jobid']%>">
-                                                                                               <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';
-                                                                                               </prop:ClientSide.OnComplete>
-                                                                                       </com:TCallback>
-                                                                                       <script type="text/javascript">
-                                                                                               document.getElementById('<%=$this->FileElementLeft->ClientID%>').addEventListener('mouseup', function(event) {
-                                                                                                       var el = event.target || event.srcElement;
-                                                                                                       if (el) {
-                                                                                                           el.style.position = 'static';
-                                                                                                           el.style.top = '-1px';
-                                                                                                           el.style.left = '-1px';
-                                                                                                       }
-                                                                                                       var fel = document.getElementById('<%=$this->FileElementLeft->ClientID%>');
-                                                                                                       fel.style.position = 'static';
-                                                                                                       fel.style.top = '-1px';
-                                                                                                       fel.style.left = '-1px';
-                                                                                               });
-                                                                                               
-                                                                                               document.getElementById('<%=$this->FileElementLeft->ClientID%>').addEventListener('click', function(event) {
-                                                                                                       var el = event.target || event.srcElement;
-                                                                                                       var vposition = el.style.left;
-                                                                                                       if(vposition == null || vposition == '0px'){
-                                                                                                               event.stop();
-                                                                                                       } else {
-                                                                                                               var request = <%=$this->FileElementCall->ActiveControl->Javascript %>;
-                                                                                                               request.dispatch();
-                                                                                                       }
-                                                                                               });
-                                                                                       </script>
-                                                                               </prop:ItemTemplate>
-                                                                       </com:TActiveTemplateColumn>
-                                                       </com:TActiveDataGrid>
+                                                                               </com:TDraggable>
+                                                                               <com:TCallback ID="FileElementCall" OnCallback="Page.getVersions" ActiveControl.CallbackParameter="<%=$this->getParent()->Data['name'] . '|' . @$this->getParent()->Data['pathid'] . '|' . @$this->getParent()->Data['filenameid'] . '|' . @$this->getParent()->Data['jobid']%>">
+                                                                                       <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';
+                                                                                       </prop:ClientSide.OnComplete>
+                                                                               </com:TCallback>
+                                                                               <script type="text/javascript">
+                                                                                       Formatters.set_formatters();
+                                                                                       document.getElementById('<%=$this->FileElementLeft->ClientID%>').addEventListener('mouseup', function(event) {
+                                                                                               var el = event.target || event.srcElement;
+                                                                                               if (el) {
+                                                                                                   el.style.position = 'static';
+                                                                                                   el.style.top = '-1px';
+                                                                                                   el.style.left = '-1px';
+                                                                                               }
+                                                                                               var fel = document.getElementById('<%=$this->FileElementLeft->ClientID%>');
+                                                                                               fel.style.position = 'static';
+                                                                                               fel.style.top = '-1px';
+                                                                                               fel.style.left = '-1px';
+                                                                                       });
+                                                                                       document.getElementById('<%=$this->FileElementLeft->ClientID%>').addEventListener('click', function(event) {
+                                                                                               var el = event.target || event.srcElement;
+                                                                                               if (el.className == 'link') {
+                                                                                                       // 'Add' link doesn't cause sending request
+                                                                                                       return;
+                                                                                               }
+                                                                                               var vposition = el.style.left;
+                                                                                               if(vposition == null || vposition == '0px'){
+                                                                                                       event.stop();
+                                                                                               } else {
+                                                                                                       var request = <%=$this->FileElementCall->ActiveControl->Javascript %>;
+                                                                                                       request.dispatch();
+                                                                                               }
+                                                                                       });
+                                                                               </script>
+                                                                       </prop:ItemTemplate>
+                                                               </com:TActiveTemplateColumn>
+                                               </com:TActiveDataGrid>
                                                </div>
                                        </td>
                                        <td>
                                                                        <prop:ItemTemplate>
                                                                                <com:TDraggable ID="FileElementToRestore" Revert="false" Handle="<%=$this->FileElementToRestore->ClientID%>" Ghosting="true" CssClass="draggable" Style="float: left">
                                                                                        <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/<%=($this->getParent()->Data['type'] == 'dir' ? 'directory-icon' : 'file-icon')%>.png" alt="<%=$this->getParent()->Data['type'] == 'dir' ? 'directory' : 'file'%>" />
-                                                                                       <com:TLabel Text="<%=$this->getParent()->Data['name']%>" Width="50%" /> <%=is_array($this->getParent()->Data['lstat']) && array_key_exists('mtime', $this->getParent()->Data['lstat']) ? date("Y-m-d H:i:s", $this->getParent()->Data['lstat']['mtime']) : ''%>
+                                                                                       <span><%=$this->getParent()->Data['name']%></span> &raquo; MTIME:<%=date("Y-m-d H:i:s", $this->getParent()->Data['lstat']['mtime'])%> &raquo; <%[ Size: ]%><span class="size"><%=$this->getParent()->Data['lstat']['size']%></span>
                                                                                </com:TDraggable>
-                                                                               <com:TActiveLinkButton CssClass="link" Style="float: right;" OnCallback="Page.addFileToRestore" ActiveControl.CallbackParameter="<%=isset($this->getParent()->Data['fileid']) ? $this->getParent()->Data['fileid'] : false%>" ActiveControl.EnableUpdate="false" Text="<%[ Add ]%>"/>
+                                                                               <com:TActiveLinkButton
+                                                                                       CssClass="link"
+                                                                                       Style="float: right;"
+                                                                                       OnCallback="Page.addFileToRestore"
+                                                                                       ActiveControl.CallbackParameter="<%=isset($this->getParent()->Data['fileid']) ? $this->getParent()->Data['fileid'] : false%>"
+                                                                                       ActiveControl.EnableUpdate="false"
+                                                                                       Text="<%[ Add ]%>"
+                                                                               />
                                                                                <script type="text/javascript">
+                                                                                       Formatters.set_formatters();
+                                                                                       document.getElementById('<%=$this->FileElementToRestore->ClientID%>').addEventListener('mouseover', function(event) {
+                                                                                               var tip_fields = [
+                                                                                                       '<%[ JobId: ]%> <%=$this->getParent()->Data['jobid']%>',
+                                                                                                       '<%[ Volume: ]%> <%=$this->getParent()->Data['volname']%>',
+                                                                                                       '<%[ InChanger: ]%> <%=$this->getParent()->Data['inchanger'] ? Prado::localize('Yes') : Prado::localize('No')%>',
+                                                                                                       '<%[ Sum: ]%> <%=$this->getParent()->Data['md5']%>'
+                                                                                               ];
+                                                                                               showTip(this, '<%=$this->getParent()->Data['name']%>', tip_fields.join('<br />'));
+                                                                                       });
                                                                                        document.getElementById('<%=$this->FileElementToRestore->ClientID%>').addEventListener('mouseup', function(event) {
                                                                                                var el = event.target || event.srcElement;
                                                                                                if (el) {
                                                                                DataKeyField="fileid"
                                                                        >
                                                                        <prop:EmptyTemplate>
-                                                                               <div class="file-browser-watermark" style="padding: 50px 10px">
+                                                                               <div class="file-browser-watermark" style="padding: 110px 10px">
                                                                                        <%[ For add a file to restore please click 'Add' link or drag a file from frame on left or from above frame and drop it here ]%>
                                                                                </div>
                                                                        </prop:EmptyTemplate>
-                                                                               <com:TActiveTemplateColumn>
-                                                                                       <prop:ItemTemplate>
-                                                                                                       <com:TPanel ID="FileElement" Style="clear: both">
-                                                                                                               <com:TActiveImageButton ImageUrl="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_close.png" Style="float: right; vertical-align: text-bottom;margin: 3px;" OnCallback="Page.removeSelectedFile" ActiveControl.CallbackParameter="<%=isset($this->getParent()->Data['fileid']) ? $this->getParent()->Data['fileid'] : ''%>"/>
-                                                                                                               <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/<%=($this->getParent()->Data['type'] == 'dir' ? 'directory-icon' : 'file-icon')%>.png" alt="<%=$this->getParent()->Data['type'] == 'dir' ? 'directory' : 'file'%>" />
-                                                                                                               <com:TLabel Text="<%=$this->getParent()->Data['name']%>" /> <%=is_array($this->getParent()->Data['lstat']) && array_key_exists('mtime', $this->getParent()->Data['lstat']) ? date("Y-m-d H:i:s", $this->getParent()->Data['lstat']['mtime']) : ''%>
-                                                                                                       </com:TPanel>
-                                                                                       </prop:ItemTemplate>
-                                                                               </com:TActiveTemplateColumn>
+                                                                       <com:TActiveTemplateColumn>
+                                                                               <prop:ItemTemplate>
+                                                                                               <com:TPanel ID="FileElement" Style="clear: both">
+                                                                                                       <com:TActiveImageButton ImageUrl="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_close.png" Style="float: right; vertical-align: text-bottom;margin: 3px;" OnCallback="Page.removeSelectedFile" ActiveControl.CallbackParameter="<%=isset($this->getParent()->Data['fileid']) ? $this->getParent()->Data['fileid'] : ''%>"/>
+                                                                                                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/<%=($this->getParent()->Data['type'] == 'dir' ? 'directory-icon' : 'file-icon')%>.png" alt="<%=$this->getParent()->Data['type'] == 'dir' ? 'directory' : 'file'%>" />
+                                                                                                       <span><%=$this->getParent()->Data['name']%></span> &raquo; MTIME:<%=date("Y-m-d H:i:s", $this->getParent()->Data['lstat']['mtime'])%> &raquo; <%[ Size: ]%><span class="size"><%=$this->getParent()->Data['lstat']['size']%></span>
+                                                                                               </com:TPanel>
+                                                                                               <script type="text/javascript">
+                                                                                                       Formatters.set_formatters();
+                                                                                               </script>
+                                                                               </prop:ItemTemplate>
+                                                                       </com:TActiveTemplateColumn>
                                                                        </com:TActiveDataGrid>
                                                                </com:TDropContainer>
                                                </div>
                        <div class="line">
                                <div class="text"><com:TLabel ForControl="RestoreClient" Text="<%[ Restore to client: ]%>" /></div>
                                <div class="field">
-                                       <com:TActiveDropDownList ID="RestoreClient" CssClass="textbox" OnLoad="setRestoreClients" CausesValidation="false" />
+                                       <com:TDropDownList ID="RestoreClient" CssClass="textbox" CausesValidation="false" />
                                </div>
                        </div>
                        <div class="line">
                                <div class="text"><com:TLabel ForControl="RestorePath" Text="<%[ Restore to directory: ]%>" /></div>
                                <div class="field"><com:TTextBox ID="RestorePath" CssClass="textbox" Width="350px" Text="/tmp/restore" /></div>
                        </div>
+                       <hr style="margin-top: 50px" />
+                       <p><%[ During restore there will be used following volumes: ]%></p>
+                       <com:TRepeater ID="RestoreVolumes">
+                               <prop:HeaderTemplate>
+                                       <table style="min-width: 450px">
+                                               <tr>
+                                                       <th><%[ In Changer ]%></th>
+                                                       <th><%[ Volume ]%></th>
+                                               </tr>
+                               </prop:HeaderTemplate>
+                               <prop:ItemTemplate>
+                                               <tr>
+                                                       <td class="center"><com:TLabel Text="<%#($this->DataItem['inchanger'] == 1 ? Prado::localize('Yes') : Prado::localize('No'))%>" /></td>
+                                                       <td> <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/tape_tools_icon.png" alt="" /> <com:TLabel Text="<%#$this->DataItem['volume']%>" /></td>
+                                               </tr>
+                               </prop:ItemTemplate>
+                               <prop:FooterTemplate>
+                                       </table>
+                               </prop:FooterTemplate>
+                       </com:TRepeater>
                </com:TWizardStep>
                <com:TWizardStep ID="Step5" Title="<%[ Step 5 - options for restore ]%>" StepType="Auto">
                        <div class="line">
                                <div class="text"><com:TLabel ForControl="RestoreJob" Text="<%[ Restore job: ]%>" /></div>
                                <div class="field">
-                                       <com:TActiveDropDownList ID="RestoreJob" CssClass="textbox" Width="350px" CausesValidation="false" />
+                                       <com:TDropDownList ID="RestoreJob" CssClass="textbox" Width="350px" CausesValidation="false" />
                                </div>
                        </div>
                        <div class="line">
                                        <com:TTextBox ID="RestoreJobPriority" CssClass="textbox" CausesValidation="false" Width="60px" MaxLength="5" Text="10" />
                                </div>
                        </div>
+                       <div class="line" style="margin-bottom: 10px">
+                               <div class="text" style="vertical-align: top"><%[ File relocation option:  ]%></div>
+                               <div class="field">
+                                       <input type="radio" name="file_relocation" id="file_relocation_1" value="1" onclick="switch_file_relocation_mode(this.value)" /> <label for="file_relocation_1"><%[ Do not relocate files ]%></label><br />
+                                       <input type="radio" name="file_relocation" id="file_relocation_2" value="2" onclick="switch_file_relocation_mode(this.value)" /> <label for="file_relocation_2"><%[ Relocate files with prefix and/or suffix ]%></label><br />
+                                       <input type="radio" name="file_relocation" id="file_relocation_3" value="3" onclick="switch_file_relocation_mode(this.value)" /> <label for="file_relocation_3"><%[ Relocate files with regular expression ]%></label>
+                               </div>
+                       </div>
+                       <div id="file_relocation_prefix_suffix" style="display: none">
+                               <div class="line">
+                                       <div class="text"><com:TLabel ForControl="RestoreStripPrefix" Text="<%[ Strip prefix: ]%>" /></div>
+                                       <div class="field">
+                                               <com:TTextBox ID="RestoreStripPrefix" CssClass="textbox" Width="350px" CausesValidation="false" />
+                                       </div>
+                               </div>
+                               <div class="line">
+                                       <div class="text"><com:TLabel ForControl="RestoreAddPrefix" Text="<%[ Add prefix: ]%>" /></div>
+                                       <div class="field">
+                                               <com:TTextBox ID="RestoreAddPrefix" CssClass="textbox" Width="350px" CausesValidation="false" />
+                                       </div>
+                               </div>
+                               <div class="line">
+                                       <div class="text"><com:TLabel ForControl="RestoreAddSuffix" Text="<%[ Add suffix: ]%>" /></div>
+                                       <div class="field">
+                                               <com:TTextBox ID="RestoreAddSuffix" CssClass="textbox" Width="350px" CausesValidation="false" />
+                                       </div>
+                               </div>
+                       </div>
+                       <div id="file_relocation_regex_where" style="display: none">
+                               <div class="line">
+                                       <div class="text"><com:TLabel ForControl="RestoreRegexWhere" Text="<%[ RegexWhere: ]%>" /></div>
+                                       <div class="field">
+                                               <com:TTextBox ID="RestoreRegexWhere" CssClass="textbox" Width="350px" CausesValidation="false" />
+                                       </div>
+                               </div>
+                       </div>
+                       <script type="text/javascript">
+                               function switch_file_relocation_mode(opt) {
+                                       var prefix_suffix_opts = document.getElementById('file_relocation_prefix_suffix');
+                                       var regex_where_opts = document.getElementById('file_relocation_regex_where');
+                                       var radio = document.getElementById('file_relocation_' + opt);
+                                       if (opt == 1) { // don't use file relocation
+                                               prefix_suffix_opts.style.display = 'none';
+                                               regex_where_opts.style.display = 'none';
+                                               radio.checked = true;
+                                       } else if (opt == 2) { // use prefix/suffix relocation
+                                               prefix_suffix_opts.style.display = '';
+                                               regex_where_opts.style.display = 'none';
+                                               radio.checked = true;
+                                       } else if (opt == 3) { // use regex_where relocation
+                                               prefix_suffix_opts.style.display = 'none';
+                                               regex_where_opts.style.display = '';
+                                               radio.checked = true;
+                                       }
+                               }
+                               var relocation_opt = '<%=$this->file_relocation_opt%>';
+                               if (relocation_opt) {
+                                       switch_file_relocation_mode(relocation_opt);
+                               } else {
+                                       switch_file_relocation_mode(1); // default setting
+                               }
+                       </script>
                </com:TWizardStep>
                <com:TWizardStep ID="Step6" Title="<%[ Step 6 - Finish ]%>" StepType="Finish">
                        <fieldset>
                                <div class="line">
                                        <div class="text"><%[ Backup for restore: ]%></div>
                                        <div class="field bold">
-                                               <com:TLabel Text="<%=@$this->getModule('api')->get(array('jobs', $this->BackupToRestore->SelectedValue))->output->job%>" Visible="<%=$this->OnlySelectedBackupSelection->Checked%>" />
                                                <com:TLabel Text="<%=$this->GroupBackupToRestore->SelectedValue%>" Visible="<%=$this->GroupBackupSelection->Checked%>" />
                                        </div>
                                </div>
                                        <div class="text"><%[ Restore job priority: ]%></div>
                                        <div class="field bold"><%=$this->RestoreJobPriority->Text%></div>
                                </div>
+                               <div class="line">
+                                       <div class="text"><%[ File relocation option: ]%></div>
+                                       <div class="field bold">
+                                               <com:TLabel Visible="<%=$this->file_relocation_opt == 1%>" Text="<%[ Do not relocate files ]%>"/>
+                                               <com:TLabel Visible="<%=$this->file_relocation_opt == 2%>" Text="<%[ Relocate files with prefix and/or suffix ]%>"/>
+                                               <com:TLabel Visible="<%=$this->file_relocation_opt == 3%>" Text="<%[ Relocate files with regular expression ]%>"/>
+                                       </div>
+                               </div>
+                               <div style="display: <%=$this->file_relocation_opt == 2 ? 'block' : 'none'%>">
+                                       <div class="line">
+                                               <div class="text"><%[ Strip prefix: ]%></div>
+                                               <div class="field bold"><%=$this->RestoreStripPrefix->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ Add prefix: ]%></div>
+                                               <div class="field bold"><%=$this->RestoreAddPrefix->Text%></div>
+                                       </div>
+                                       <div class="line">
+                                               <div class="text"><%[ Add suffix: ]%></div>
+                                               <div class="field bold"><%=$this->RestoreAddSuffix->Text%></div>
+                                       </div>
+                               </div>
+                               <div style="display: <%=$this->file_relocation_opt == 3 ? 'block' : 'none'%>">
+                                       <div class="line">
+                                               <div class="text"><%[ RegexWhere: ]%></div>
+                                               <div class="field bold"><%=$this->RestoreRegexWhere->Text%></div>
+                                       </div>
+                               </div>
                        </fieldset>
                </com:TWizardStep>
        </com:TWizard>
index f395a7812bf227f07aac8f7fb42ff16493835b84..aeffaec46aa0daac8910bb3e94d6021320bc4b88 100644 (file)
 Prado::using('Application.Web.Class.BaculumWebPage'); 
 Prado::using('System.Exceptions.TException');
 Prado::using('System.Web.UI.WebControls.TWizard');
-Prado::using('System.Web.UI.ActiveControls.TActiveDropDownList');
-Prado::using('System.Web.UI.ActiveControls.TActivePanel');
+Prado::using('System.Web.UI.WebControls.TDataGrid');
 Prado::using('System.Web.UI.ActiveControls.TActiveLinkButton');
 Prado::using('System.Web.UI.ActiveControls.TActiveImageButton');
 Prado::using('System.Web.UI.ActiveControls.TDropContainer');
 Prado::using('System.Web.UI.ActiveControls.TDraggable');
-Prado::using('System.Web.UI.ActiveControls.TActiveRadioButton');
 Prado::using('System.Web.UI.ActiveControls.TActiveDataGrid');
 Prado::using('System.Web.UI.ActiveControls.TCallback');
+Prado::using('System.Web.UI.ActiveControls.TActiveTextBox');
 
 class RestoreWizard extends BaculumWebPage
 {
-       private $job_levels_to_restore = array('F' => 'Full', 'I' => 'Incremental', 'D'=> 'Differential');
-       private $jobs = null;
-       private $filesets = null;
-       private $storages = null;
-       private $clients = null;
-       private $browser_root_dir = array('name' => '.', 'type' => 'dir');
+       /**
+        * Job levels allowed to restore.
+        */
+
+       private $joblevel = array('F', 'I', 'D');
+       /**
+        * Job statuses allowed to restore.
+        */
+       private $jobstatus = array('T', 'W', 'A', 'E', 'e', 'f');
+
+       /**
+        * File browser special directories.
+        */
+       private $browser_root_dir = array('name' => '.', 'type' => 'dir', 'fileid' => null);
        private $browser_up_dir = array('name' => '..', 'type' => 'dir');
 
+       /**
+        * Used to provide in template selected by user single jobid to restore.
+        */
+       public $restore_single_jobid;
+
+       /**
+        * Stores file relocation option. Used in template.
+        */
+       public $file_relocation_opt;
+
+       /**
+        * FIle browser elements for which 'Add' button is unavailable.
+        */
        public $excluded_elements_from_add = array('.', '..');
 
+       /**
+        * Prefix for Bvfs path.
+        */
        const BVFS_PATH_PREFIX = 'b2';
 
        public function onInit($param) {
                parent::onInit($param);
                if(!$this->IsPostBack && !$this->IsCallBack) {
-                       $this->setBrowserFiles(array());
-                       $this->setFileVersions(array());
-                       $this->setFilesToRestore(array());
-                       $this->markFileToRestore(null, null);
-                       $this->setRestoreJobs();
-                       $_SESSION['restore_path'] = array();
+                       $this->resetWizard();
+                       $this->loadBackupClients();
                }
        }
 
-       public function onLoad($param) {
-               parent::onLoad($param);
-                       if($this->RestoreWizard->ActiveStepIndex == 0) {
-                               $this->jobs = $this->getModule('api')->get(array('jobs'))->output;
-                               $this->filesets = $this->getModule('api')->get(array('filesets', 'info'))->output;
+       /**
+        * Wizard next button callback actions.
+        *
+        * @param TWizard $sender sender object
+        * @param TWizardNavigationEventParameter $param sender parameters
+        * @return none
+        */
+       public function wizardNext($sender, $param) {
+               if ($param->CurrentStepIndex === 0) {
+                       $this->loadBackupsForClient();
+                       $this->loadGroupBackupToRestore();
+                       $this->loadGroupFileSetToRestore();
+                       $this->loadRestoreClients();
+                       if (isset($_SESSION['restore_single_jobid'])) {
+                               $this->restore_single_jobid = $_SESSION['restore_single_jobid'];
                        }
-                       $this->clients = $this->getModule('api')->get(array('clients'))->output;
-
-                       if(!$this->IsCallBack && (($this->RestoreWizard->ActiveStepIndex == 1 && $this->getPage()->BackupToRestore->ItemCount > 0) || $this->RestoreWizard->ActiveStepIndex == 3)) {
-                               $this->setFileVersions(array());
-                               $this->setBrowserPath('');
-                               $this->prepareFilesToRestore();
-                               $this->prepareVersionsToRestore();
+               } elseif ($param->CurrentStepIndex === 1) {
+                       if ($this->Request->contains('backup_to_restore')) {
+                               $_SESSION['restore_single_jobid'] = $this->Request['backup_to_restore'];
                        }
-       }
-
-       public function addFileToRestore($sender, $param) {
-               $fileid = null;
-               if (isset($param->callbackParameter)) {
-                       list(, , , $sourceElementID, , ) = explode('_', $sender->ClientID, 6);
-                       $fileid = $param->callbackParameter;
-               } else {
-                       $control = $param->getDroppedControl();
-                       $item = $control->getNamingContainer();
-                       list(, , , $sourceElementID, , ) = explode('_', $param->getDragElementID(), 6); // I know that it is ugly.
-               }
-               if($sourceElementID == $this->VersionsDataGrid->ID) {
-                       if (is_null($fileid)) {
-                               $fileid = $this->VersionsDataGrid->getDataKeys()->itemAt($item->getItemIndex());
+                       $this->setFileVersions();
+                       $this->loadSelectedFiles();
+                       $this->loadFileVersions();
+                       $this->goToPath();
+               } elseif ($param->CurrentStepIndex === 2) {
+                       $this->loadRequiredVolumes();
+               } elseif ($param->CurrentStepIndex === 3) {
+                       if (isset($_SESSION['file_relocation'])) {
+                               $this->file_relocation_opt = $_SESSION['file_relocation'];
                        }
-                       $fileProperties = $this->getFileVersions($fileid);
-               } else {
-                       if (is_null($fileid)) {
-                               $fileid = $this->DataGridFiles->getDataKeys()->itemAt($item->getItemIndex());
+               } elseif ($param->CurrentStepIndex === 4) {
+                       if ($this->Request->contains('file_relocation')) {
+                               $_SESSION['file_relocation'] = $this->Request['file_relocation'];
                        }
-                       $fileProperties = $this->getBrowserFile($fileid);
-               }
-               if($fileProperties['name'] != $this->browser_root_dir['name'] && $fileProperties['name'] != $this->browser_up_dir['name']) {
-                       $this->markFileToRestore($fileid, $fileProperties);
-                       $this->prepareFilesToRestore();
+                       $this->file_relocation_opt = $_SESSION['file_relocation'];
                }
        }
 
-       public function removeSelectedFile($sender, $param) {
-               $fileid = $param->CallbackParameter;
-               $this->unmarkFileToRestore($fileid);
-               $this->prepareFilesToRestore();
-       }
-
-       public function getVersions($sender, $param) {
-               list($filename, $pathid, $filenameid, $jobid) = explode('|', $param->CallbackParameter, 4);
-               if($filenameid == 0) {
-                       $this->setBrowserPath($filename);
-                       return;
+       /**
+        * Wizard prev button callback actions.
+        *
+        * @param TWizard $sender sender object
+        * @param TWizardNavigationEventParameter $param sender parameters
+        * @return none
+        */
+       public function wizardPrev($sender, $param) {
+               if ($param->CurrentStepIndex === 2) {
+                       $this->restore_single_jobid = $_SESSION['restore_single_jobid'];
+                       $this->loadBackupsForClient();
+               } elseif ($param->CurrentStepIndex === 3) {
+                       $this->setFileVersions();
+                       $this->loadSelectedFiles();
+                       $this->loadFileVersions();
+                       $this->goToPath();
+               } elseif ($param->CurrentStepIndex === 5) {
+                       $this->file_relocation_opt = $_SESSION['file_relocation'];
                }
-               $clientname = $this->BackupClientName->SelectedValue;
-               $versions = $this->getModule('api')->get(array('bvfs', 'versions', $clientname, $jobid, $pathid, $filenameid))->output;
-               $fileVersions = $this->getModule('misc')->parseFileVersions($filename, $versions);
-               $this->setFileVersions($fileVersions);
-               $this->VersionsDataGrid->dataSource = $fileVersions;
-               $this->VersionsDataGrid->dataBind();
-               $this->prepareFilesToRestore();
-       }
-
-       public function refreshSelectedFiles($sender, $param) {
-               $this->prepareFilesToRestore();
-               $this->SelectedVersionsDropper->render($param->NewWriter);
-       }
-
-       public function NextStep($sender, $param) {
-       }
-       
-       public function PreviousStep($sender, $param) {
        }
 
+       /**
+        * Cancel wizard.
+        *
+        * @return none
+        */
        public function wizardStop($sender, $param) {
+               $this->resetWizard();
                $this->goToDefaultPage();
        }
 
-       public function setJobs($sender, $param) {
-               $jobs_list = $jobs_group_list = array();
-               $clientid = null;
-               for ($i = 0; $i < count($this->clients); $i++) {
-                       if ($this->clients[$i]->name === $this->BackupClientName->SelectedValue) {
-                               $clientid = $this->clients[$i]->clientid;
-                               break;
-                       }
-               }
-               if(is_array($this->jobs)) {
-                       foreach($this->jobs as $job) {
-                               if(array_key_exists($job->level, $this->job_levels_to_restore) && $job->type == 'B' && $job->jobstatus == 'T' && $job->clientid == $clientid) {
-                                       $jobs_list[$job->jobid] = sprintf('[%s] %s, %s, %s', $job->jobid, $job->name, $this->job_levels_to_restore[$job->level], $job->endtime);
-                                       $jobs_group_list[$job->name] = $job->name;
-                               }
-                       }
-               }
-
-               $fileset_list = array();
-               for ($i = 0; $i < count($this->filesets); $i++) {
-                       $fileset_list[$this->filesets[$i]->filesetid] = $this->filesets[$i]->fileset . ' (' . $this->filesets[$i]->createtime . ')';
-               }
-               asort($fileset_list);
-
-               $this->BackupToRestore->dataSource = $jobs_list;
-               $this->BackupToRestore->dataBind();
-               $this->GroupBackupToRestore->dataSource = $jobs_group_list;
-               $this->GroupBackupToRestore->dataBind();
-               $this->GroupBackupFileSet->dataSource = $fileset_list;
-               $this->GroupBackupFileSet->dataBind();
-               $this->setRestoreClients($sender, $param);
-       }
-
-       public function setBackupClients($sender, $param) {
-               if(!$this->IsPostBack) {
-                       $client_list = array();
-                       foreach($this->clients as $client) {
-                               $client_list[$client->name] = $client->name;
-                       }
-                       $this->BackupClientName->dataSource = $client_list;
-                       $this->BackupClientName->dataBind();
+       /**
+        * Load backup clients list (step 1).
+        *
+        * @param TActiveDropDownList $sender sender object
+        * @param TCommandParameter $param parameters object
+        * @return none
+        */
+       public function loadBackupClients() {
+               $client_list = array();
+               $clients = $this->getModule('api')->get(array('clients'))->output;
+               for ($i = 0; $i < count($clients); $i++) {
+                       $client_list[$clients[$i]->name] = $clients[$i]->name;
                }
-       }
-
-       public function setRestoreClients($sender, $param) {
-               if(!$this->IsPostBack || $sender->ID == $this->BackupClientName->ID) {
-                       $client_list = array();
-                       foreach($this->clients as $client) {
-                               $client_list[$client->name] = $client->name;
-                       }
-                       $this->RestoreClient->SelectedValue = $this->BackupClientName->SelectedValue;
-                       $this->RestoreClient->dataSource = $client_list;
-                       $this->RestoreClient->dataBind();
+               asort($client_list);
+               $this->BackupClientName->dataSource = $client_list;
+               $this->BackupClientName->dataBind();
+       }
+
+       /**
+        * Load restore client list.
+        *
+        * @return none
+        */
+       public function loadRestoreClients() {
+               $client_list = array();
+               $clients = $this->getModule('api')->get(array('clients'))->output;
+               for ($i = 0; $i < count($clients); $i++) {
+                       $client_list[$clients[$i]->name] = $clients[$i]->name;
                }
-       }
-
-       public function setBackupClient($sender, $param) {
-               $this->ClientToRestore->SelectedValue = $param->SelectedValue;
-       } 
-
-       public function setBackupSelection($sender, $param) {
+               $this->RestoreClient->SelectedValue = $this->BackupClientName->SelectedValue;
+               $this->RestoreClient->dataSource = $client_list;
+               $this->RestoreClient->dataBind();
+       }
+
+       /**
+        * Load backups for selected client (Step 2).
+        *
+        * @return none
+        */
+       public function loadBackupsForClient() {
+               $clientid = $this->getBackupClientId();
+               $jobs_for_client = $this->getModule('api')->get(array('clients', 'jobs', $clientid))->output;
+               $jobs = $this->getModule('misc')->objectToArray($jobs_for_client);
+               $this->BackupsToRestore->DataSource = array_filter($jobs, array($this, 'isBackupJobToRestore'));
+               $this->BackupsToRestore->dataBind();
+       }
+
+       /**
+        * Check if job can be used in restore.
+        *
+        * @param array $job job properties
+        * @return true if job should be listed to restore, otherwise false
+        */
+       private function isBackupJobToRestore($job) {
+               return ($job['type'] === 'B' && in_array($job['level'], $this->joblevel) && in_array($job['jobstatus'], $this->jobstatus));
+       }
+
+       public function loadBackupSelection($sender, $param) {
                $this->GroupBackupToRestoreField->Display = ($sender->ID == $this->GroupBackupSelection->ID) ? 'Dynamic' : 'None';
                $this->BackupToRestoreField->Display = ($sender->ID == $this->OnlySelectedBackupSelection->ID) ? 'Dynamic' : 'None';
-               $this->setBrowserFiles(array());
-               $this->setFileVersions(array());
-               $this->setFilesToRestore(array());
+               $this->setBrowserFiles();
+               $this->setFileVersions();
+               $this->setFilesToRestore();
                $this->markFileToRestore(null, null);
                $_SESSION['restore_path'] = array();
        }
 
-       public function resetFileBrowser($sender, $param) {
-               $this->markFileToRestore(null, null);
-               $this->setBrowserPath($this->browser_root_dir['name']);
-       }
-
-       private function prepareBrowserFiles($files) {
-               $this->setBrowserFiles($files);
-               $this->DataGridFiles->dataSource = $files;
-               @$this->DataGridFiles->dataBind();
-       }
-
-       private function prepareVersionsToRestore() {
-               $this->VersionsDataGrid->dataSource = $_SESSION['files_versions'];
-               $this->VersionsDataGrid->dataBind();
-       }
 
-       private function prepareFilesToRestore() {
-               $this->SelectedVersionsDataGrid->dataSource = $_SESSION['restore'];
-               $this->SelectedVersionsDataGrid->dataBind();
-       }
-
-       private function setBrowserPath($path) {
-               if(!empty($path)) {
-                       if($path == $this->browser_up_dir['name']) {
-                               array_pop($_SESSION['restore_path']);
-                       } elseif($path == $this->browser_root_dir['name']) {
-                               $_SESSION['restore_path'] = array();
-                       } else {
-                               array_push($_SESSION['restore_path'], $path);
+       /**
+        * Get selected backup client identifier.
+        *
+        * @return mixed client identifier or null if no clientid found
+        */
+       public function getBackupClientId() {
+               $clientid = null;
+               $clients = $this->getModule('api')->get(array('clients'))->output;
+               for ($i = 0; $i < count($clients); $i++) {
+                       if ($clients[$i]->name === $this->BackupClientName->SelectedValue) {
+                               $clientid = $clients[$i]->clientid;
+                               break;
+                       }
+               }
+               return $clientid;
+       }
+
+       /**
+        * Load backup jobs to restore for group most recent backups feature.
+        *
+        * @return none
+        */
+       public function loadGroupBackupToRestore() {
+               $jobs = $this->getModule('api')->get(array('jobs'))->output;
+               $jobs = $this->getModule('misc')->objectToArray($jobs);
+               $clientid = $this->getBackupClientId();
+               $job_group_list = array();
+               for ($i = 0; $i < count($jobs); $i++) {
+                       $job = $this->getModule('misc')->objectToArray($jobs[$i]);
+                       if ($this->isBackupJobToRestore($jobs[$i]) && $jobs[$i]['clientid'] === $clientid) {
+                               $job_group_list[$jobs[$i]['name']] = $jobs[$i]['name'];
                        }
                }
-               $this->setBrowserLocalizator();
-               $this->prepareBrowserContent();
-       }
 
-       private function getBrowserPath($stringFormat = false) {
-               return ($stringFormat === true) ? implode($_SESSION['restore_path']) : $_SESSION['restore_path'];
+               $this->GroupBackupToRestore->dataSource = $job_group_list;
+               $this->GroupBackupToRestore->dataBind();
        }
 
-       private function setBrowserLocalizator() {
-               $localization = $this->getBrowserPath(true);
-               $this->PathField->HeaderText = mb_strlen($localization) > 56 ? '...' . mb_substr($localization, -56) : $localization;
+       /**
+        * Load filesets to restore for group most recent backups feature.
+        *
+        * @return none
+        */
+       public function loadGroupFileSetToRestore() {
+               $filesets = $this->getModule('api')->get(array('filesets', 'info'))->output;
+               $fileset_list = array();
+               for ($i = 0; $i < count($filesets); $i++) {
+                       $fileset_list[$filesets[$i]->filesetid] = $filesets[$i]->fileset . ' (' . $filesets[$i]->createtime . ')';
+               }
+               asort($fileset_list);
+
+               $this->GroupBackupFileSet->dataSource = $fileset_list;
+               $this->GroupBackupFileSet->dataBind();
        }
 
+       /**
+        * Prepare left file browser content.
+        *
+        * @return none
+        */
        private function prepareBrowserContent() {
                $jobids = $this->getElementaryBackup();
                $elements = array();
-               if (!is_null($jobids)) {
-                       // generating Bvfs may takes a moment
-                       $this->generateBvfsCacheByJobids($jobids);
-                       $bvfs_dirs_list = $this->getModule('api')->set(array('bvfs', 'lsdirs'), array('jobids' => $jobids, 'path' => $this->getBrowserPath(true)));
-                       $bvfs_dirs = is_object($bvfs_dirs_list) ? $bvfs_dirs_list->output : array();
-                       $dirs = $this->getModule('misc')->parseBvfsList($bvfs_dirs);
-                       $bvfs_files_list = $this->getModule('api')->set(array('bvfs', 'lsfiles'), array('jobids' => $jobids, 'path' => $this->getBrowserPath(true)));
-                       $bvfs_files = is_object($bvfs_files_list) ? $bvfs_files_list->output : array();
-                       $files = $this->getModule('misc')->parseBvfsList($bvfs_files);
+               if (!empty($jobids)) {
+                       // generating Bvfs may take a moment
+                       $this->generateBvfsCache($jobids);
+
+                       // get directories list
+                       $bvfs_dirs = $this->getModule('api')->set(
+                               array('bvfs', 'lsdirs'),
+                               array('jobids' => $jobids, 'path' => implode($_SESSION['restore_path']))
+                       );
+                       $dirs = $this->getModule('misc')->parseBvfsList($bvfs_dirs->output);
+
+                       // get files list
+                       $bvfs_files = $this->getModule('api')->set(
+                               array('bvfs', 'lsfiles'),
+                               array('jobids' => $jobids, 'path' =>  implode($_SESSION['restore_path']))
+                       );
+                       $files = $this->getModule('misc')->parseBvfsList($bvfs_files->output);
+
                        $elements = array_merge($dirs, $files);
-                       if(count($this->getBrowserPath()) > 0) {
+                       if(count($_SESSION['restore_path']) > 0) {
                                array_unshift($elements, $this->browser_root_dir);
                        }
                }
-               $this->prepareBrowserFiles($elements);
+               $this->loadBrowserFiles($elements);
        }
 
+       /*
+        * Get single elementary backup job identifiers.
+        *
+        * @return string comma separated job identifiers
+        */
        private function getElementaryBackup() {
-               $jobids = null;
-               if($this->OnlySelectedBackupSelection->Checked === true) {
-                       $jobs = $this->getModule('api')->get(array('bvfs', 'getjobids', $this->BackupToRestore->SelectedValue));
+               $jobids = '';
+               if($this->OnlySelectedBackupSelection->Checked && isset($_SESSION['restore_single_jobid'])) {
+                       $jobs = $this->getModule('api')->get(
+                               array('bvfs', 'getjobids', $_SESSION['restore_single_jobid'])
+                       );
                        $ids = is_object($jobs) ? $jobs->output : array();
-                       foreach($ids as $jobid) {
+                       foreach ($ids as $jobid) {
                                if(preg_match('/^([\d\,]+)$/', $jobid, $match) == 1) {
                                        $jobids = $match[1];
                                        break;
@@ -294,7 +333,7 @@ class RestoreWizard extends BaculumWebPage
                                $this->GroupBackupFileSet->SelectedValue
                        );
                        $jobs_recent = $this->getModule('api')->get($params);
-                       if(count($jobs_recent->output) > 0) {
+                       if (count($jobs_recent->output) > 0) {
                                $ids = $jobs_recent->output;
                                $jobids = implode(',', $ids);
                        }
@@ -302,14 +341,210 @@ class RestoreWizard extends BaculumWebPage
                return $jobids;
        }
 
-       private function generateBvfsCacheByJobids($jobids) {
-               $this->getModule('api')->set(array('bvfs', 'update'), array('jobids' => $jobids));
+       /**
+        * Load path callback method.
+        * Used for manually typed paths in path field.
+        *
+        * @param TActiveLinkButton $sender sender object
+        * @param TEventParameter $param events parameter
+        * @return none
+        */
+       public function loadPath($sender, $param) {
+               $path = explode('/', $this->PathField->Text);
+               $path_len = count($path);
+               for ($i = 0; $i < count($path); $i++) {
+                       if ($i == ($path_len - 1) && empty($path[$i])) {
+                               // last path dir is slash so not add slash to last element
+                               break;
+                       }
+                       $path[$i] .= '/';
+               }
+               $this->goToPath($path, true);
+       }
+
+       /**
+        * Go to specific path in the file browser.
+        * There is possible to pass both single directory 'somedir'
+        * or whole path '/etc/somedir'.
+        *
+        * @param string $path path to go
+        * @param bool $full_path determines if $path param is full path or relative path (singel directory)
+        * @return none
+        */
+       private function goToPath($path = '', $full_path = false) {
+               if(!empty($path) && !$full_path) {
+                       if($path == $this->browser_up_dir['name']) {
+                               array_pop($_SESSION['restore_path']);
+                       } elseif($path == $this->browser_root_dir['name']) {
+                               $_SESSION['restore_path'] = array();
+                       } else {
+                               array_push($_SESSION['restore_path'], $path);
+                       }
+               }
+               if ($full_path && is_array($path)) {
+                       $_SESSION['restore_path'] = $path;
+               }
+               $this->setBrowserPath();
+               $this->prepareBrowserContent();
+       }
+
+       /**
+        * Add/mark file to restore.
+        * Used as callback to drag&drop browser elements.
+        *
+        * @param object $sender sender object
+        * @param object $param param object
+        * @return none
+        */
+       public function addFileToRestore($sender, $param) {
+               $fileid = null;
+               $source_element_id = null;
+               $file_prop = array();
+               if (isset($param->CallbackParameter)) {
+                       $id_parts = explode('_', $sender->ClientID, 6);
+                       $source_element_id = $id_parts[3];
+                       $fileid = $param->CallbackParameter;
+               } else {
+                       $control = $param->getDroppedControl();
+                       $item = $control->getNamingContainer();
+                       $id_parts = explode('_', $param->getDragElementID(), 6);
+                       $source_element_id = $id_parts[3];
+               }
+               if($source_element_id == $this->VersionsDataGrid->ID) {
+                       if (is_null($fileid)) {
+                               $fileid = $this->VersionsDataGrid->getDataKeys()->itemAt($item->getItemIndex());
+                       }
+                       $file_prop = $this->getFileVersions($fileid);
+               } else {
+                       if (is_null($fileid)) {
+                               $fileid = $this->DataGridFiles->getDataKeys()->itemAt($item->getItemIndex());
+                       }
+                       $file_prop = $this->getBrowserFile($fileid);
+               }
+               if($file_prop['name'] != $this->browser_root_dir['name'] && $file_prop['name'] != $this->browser_up_dir['name']) {
+                       $this->markFileToRestore($fileid, $file_prop);
+                       $this->loadSelectedFiles();
+               }
+       }
+
+       /**
+        * Remove file from files marked to restre.
+        *
+        * @param TActiveImageButton $sender remove button object
+        * @param TEventParameter $param param object
+        * @return none
+        */
+       public function removeSelectedFile($sender, $param) {
+               $fileid = $param->CallbackParameter;
+               $this->unmarkFileToRestore($fileid);
+               $this->loadSelectedFiles();
+       }
+
+       /**
+        * Get file backed up versions.
+        * Called as callback on file element click.
+        *
+        * @param TCallback $sender sender object
+        * @param object $param param object
+        * @return none
+        */
+       public function getVersions($sender, $param) {
+               list($filename, $pathid, $filenameid, $jobid) = explode('|', $param->CallbackParameter, 4);
+               if($filenameid == 0) {
+                       $this->goToPath($filename);
+                       return;
+               }
+               $clientname = $this->BackupClientName->SelectedValue;
+               $versions = $this->getModule('api')->get(array('bvfs', 'versions', $clientname, $jobid, $pathid, $filenameid))->output;
+               $file_versions = $this->getModule('misc')->parseFileVersions($filename, $versions);
+               $this->setFileVersions($file_versions);
+               $this->VersionsDataGrid->dataSource = $file_versions;
+               $this->VersionsDataGrid->dataBind();
+               $this->loadSelectedFiles();
+       }
+
+       /**
+        * Refresh/re-render selected files list.
+        *
+        * @param TDropContainer $sender sender object
+        * @param TEventParameter $param param object
+        * @return none
+        */
+       public function refreshSelectedFiles($sender, $param) {
+               $this->loadSelectedFiles();
+               $this->SelectedVersionsDropper->render($param->NewWriter);
+       }
+
+       /*
+        * Load file browser files to list.
+        *
+        * @param array $files files to list.
+        * @return none
+        */
+       private function loadBrowserFiles($files) {
+               $this->setBrowserFiles($files);
+               $this->DataGridFiles->dataSource = $files;
+               $this->DataGridFiles->dataBind();
+       }
+
+       /**
+        * Load file versions area.
+        *
+        * @return none;
+        */
+       private function loadFileVersions() {
+               $this->VersionsDataGrid->dataSource = $_SESSION['files_versions'];
+               $this->VersionsDataGrid->dataBind();
+       }
+
+       /**
+        * Load selected files in drop area.
+        *
+        * @return none
+        */
+       private function loadSelectedFiles() {
+               $this->SelectedVersionsDataGrid->dataSource = $_SESSION['restore'];
+               $this->SelectedVersionsDataGrid->dataBind();
        }
 
-       private function setFileVersions($versions) {
+       /**
+        * Set file browser path field.
+        *
+        * @return none
+        */
+       private function setBrowserPath() {
+               $this->PathField->Text = implode($_SESSION['restore_path']);
+       }
+
+       /**
+        * Generate Bvfs cache by job identifiers.
+        *
+        * @param string $jobids comma separated job identifiers
+        * @return none
+        */
+       private function generateBvfsCache($jobids) {
+               $this->getModule('api')->set(
+                       array('bvfs', 'update'),
+                       array('jobids' => $jobids)
+               );
+       }
+
+       /**
+        * Set versions for selected file.
+        *
+        * @param array $versions file versions data
+        * @return none
+        */
+       private function setFileVersions($versions = array()) {
                $_SESSION['files_versions'] = $versions;
        }
 
+       /**
+        * Get file versions for specified fileid.
+        *
+        * @param integer $fileid file identifier
+        * @return none
+        */
        private function getFileVersions($fileid) {
                $versions = array();
                foreach($_SESSION['files_versions'] as $file) {
@@ -321,10 +556,22 @@ class RestoreWizard extends BaculumWebPage
                return $versions;
        }
 
-       private function setBrowserFiles($files) {
+       /**
+        * Set browser files.
+        *
+        * @param array $files file list
+        * @return none
+        */
+       private function setBrowserFiles($files = array()) {
                $_SESSION['files_browser'] = $files;
        }
 
+       /**
+        * Get browser file by fileid.
+        *
+        * @param integer $fileid file identifier
+        * @return none
+        */
        private function getBrowserFile($fileid) {
                $element = array();
                foreach($_SESSION['files_browser'] as $file) {
@@ -336,6 +583,13 @@ class RestoreWizard extends BaculumWebPage
                return $element;
        }
 
+       /**
+        * Mark file to restore.
+        *
+        * @param integer $fileid file identifier
+        * @param array $file file properties to mark
+        * @return none
+        */
        private function markFileToRestore($fileid, $file) {
                if($fileid === null) {
                        $_SESSION['restore'] = array();
@@ -344,60 +598,119 @@ class RestoreWizard extends BaculumWebPage
                }
        }
 
+       /**
+        * Unmark file to restore.
+        *
+        * @param integer $fileid file identifier
+        * @return none
+        */
        private function unmarkFileToRestore($fileid) {
                if(array_key_exists($fileid, $_SESSION['restore'])) {
                        unset($_SESSION['restore'][$fileid]);
                }
        }
 
+       /**
+        * Get files to restore.
+        *
+        * @return array list with files to restore
+        */
        public function getFilesToRestore() {
                return $_SESSION['restore'];
        }
 
-       public function setFilesToRestore($files) {
+       /**
+        * Set files to restore
+        *
+        * @param array $files files to restore
+        * @return none
+        */
+       public function setFilesToRestore($files = array()) {
                $_SESSION['restore'] = $files;
        }
 
-       public function getRestoreElements($asObject = false) {
+       /**
+        * Get all restore elements (fileids and dirids).
+        *
+        * @param bool $as_object return result as object
+        * @return array list fileids and dirids
+        */
+       public function getRestoreElements($as_object = false) {
                $fileids = array();
                $dirids = array();
-               foreach($this->getFilesToRestore() as $fileid => $properties) {
-                       if($properties['type'] == 'dir') {
+               $findexes = array();
+               foreach ($this->getFilesToRestore() as $fileid => $properties) {
+                       if ($properties['type'] == 'dir') {
                                $dirids[] = $properties['pathid'];
-                       } elseif($properties['type'] == 'file') {
+                       } elseif ($properties['type'] == 'file') {
                                $fileids[] = $fileid;
+                               if ($properties['lstat']['linkfi'] !== 0) {
+                                       $findexes[] = $properties['jobid'] . ',' . $properties['lstat']['linkfi'];
+                               }
                        }
                }
-               $ret = array('fileid' => $fileids, 'dirid' => $dirids);
-               if($asObject === true) {
+               $ret = array('fileid' => $fileids, 'dirid' => $dirids, 'findex' => $findexes);
+               if($as_object === true) {
                        $ret = (object)$ret;
                }
                return $ret;
        }
 
+       /**
+        * Wizard finish method.
+        *
+        * @return none
+        */
        public function wizardCompleted() {
                $jobids = $this->getElementaryBackup();
                $path = self::BVFS_PATH_PREFIX . getmypid();
                $restore_elements = $this->getRestoreElements();
                $cmd_props = array('jobids' => $jobids, 'path' => $path);
+               $is_element = false;
                if(count($restore_elements['fileid']) > 0) {
                        $cmd_props['fileid'] = implode(',', $restore_elements['fileid']);
+                       $is_element = true;
                }
                if(count($restore_elements['dirid']) > 0) {
                        $cmd_props['dirid'] = implode(',', $restore_elements['dirid']);
+                       $is_element = true;
                }
+               if (count($restore_elements['findex']) > 0) {
+                       $cmd_props['findex'] = implode(',', $restore_elements['findex']);
+                       $is_element = true;
+               }
+
+               $jobid = null;
+               if ($is_element) {
+                       $this->getModule('api')->create(array('bvfs', 'restore'), $cmd_props);
+                       $restore_props = array();
+                       $restore_props['rpath'] = $path;
+                       $restore_props['client'] = $this->RestoreClient->SelectedValue;
+                       $restore_props['priority'] = intval($this->RestoreJobPriority->Text);
+                       if ($_SESSION['file_relocation'] == 2) {
+                               if (!empty($this->RestoreStripPrefix->Text)) {
+                                       $restore_props['strip_prefix'] = $this->RestoreStripPrefix->Text;
+                               }
+                               if (!empty($this->RestoreAddPrefix->Text)) {
+                                       $restore_props['add_prefix'] = $this->RestoreAddPrefix->Text;
+                               }
+                               if (!empty($this->RestoreAddSuffix->Text)) {
+                                       $restore_props['add_suffix'] = $this->RestoreAddSuffix->Text;
+                               }
+                       } elseif ($_SESSION['file_relocation'] == 3) {
+                               if (!empty($this->RestoreRegexWhere->Text)) {
+                                       $restore_props['regex_where'] = $this->RestoreRegexWhere->Text;
+                               }
+                       }
+                       if (!array_key_exists('add_prefix', $restore_props)) {
+                               $restore_props['where'] =  $this->RestorePath->Text;
+                       }
+                       $restore_props['replace'] = $this->ReplaceFiles->SelectedValue;
+                       $restore_props['restorejob'] = $this->RestoreJob->SelectedValue;
 
-               $this->getModule('api')->create(array('bvfs', 'restore'), $cmd_props);
-               $restore_props = array();
-               $restore_props['rpath'] = $path;
-               $restore_props['client'] = $this->RestoreClient->SelectedValue;
-               $restore_props['priority'] = intval($this->RestoreJobPriority->Text);
-               $restore_props['where'] =  $this->RestorePath->Text;
-               $restore_props['replace'] = $this->ReplaceFiles->SelectedValue;
-               $restore_props['restorejob'] = $this->RestoreJob->SelectedValue;
-               
-               $ret = $this->getModule('api')->create(array('jobs', 'restore'), $restore_props);
-               $jobid = $this->getModule('misc')->findJobIdStartedJob($ret->output);
+                       $ret = $this->getModule('api')->create(array('jobs', 'restore'), $restore_props);
+                       $jobid = $this->getModule('misc')->findJobIdStartedJob($ret->output);
+               }
                $url_params = array('open' => 'Job');
                if (is_numeric($jobid)) {
                        $url_params['id'] = $jobid;
@@ -405,8 +718,13 @@ class RestoreWizard extends BaculumWebPage
                $this->goToDefaultPage($url_params);
        }
 
-       private function setRestoreJobs() {
-               $restore_job_tasks = $this->Application->getModule('api')->get(array('jobs', 'tasks', 'type', 'R'))->output;
+       /**
+        * Load restore jobs on the list.
+        *
+        * @return none
+        */
+       private function loadRestoreJobs() {
+               $restore_job_tasks = $this->getModule('api')->get(array('jobs', 'tasks', 'type', 'R'))->output;
                $jobs = array();
                foreach ($restore_job_tasks as $director => $restore_jobs) {
                        $jobs = array_merge($jobs, $restore_jobs);
@@ -414,5 +732,40 @@ class RestoreWizard extends BaculumWebPage
                $this->RestoreJob->DataSource = array_combine($jobs, $jobs);
                $this->RestoreJob->dataBind();
        }
+
+       private function loadRequiredVolumes() {
+               $volumes = array();
+               foreach ($this->getFilesToRestore() as $fileid => $props) {
+                       // it can be expensive for many restore paths
+                       $result = $this->getModule('api')->get(array('volumes', 'required', $props['jobid'], $fileid));
+                       if ($result->error === 0) {
+                               for ($i = 0; $i < count($result->output); $i++) {
+                                       $volumes[$result->output[$i]->volume] = array(
+                                               'volume' => $result->output[$i]->volume,
+                                               'inchanger' => $result->output[$i]->inchanger
+                                       );
+                               }
+                       }
+               }
+               $this->RestoreVolumes->DataSource = array_values($volumes);
+               $this->RestoreVolumes->dataBind();
+       }
+
+       /**
+        * Reset wizard.
+        * All fields are back to initial form.
+        *
+        * @return none
+        */
+       private function resetWizard() {
+               $this->setBrowserFiles();
+               $this->setFileVersions();
+               $this->setFilesToRestore();
+               $this->markFileToRestore(null, null);
+               $this->loadRestoreJobs();
+               $_SESSION['restore_path'] = array();
+               $_SESSION['restore_single_jobid'] = null;
+               unset($_SESSION['file_relocation']);
+       }
 }
 ?>
diff --git a/gui/baculum/themes/Baculum-v1/restore-wizard.css b/gui/baculum/themes/Baculum-v1/restore-wizard.css
new file mode 100644 (file)
index 0000000..b10ac8b
--- /dev/null
@@ -0,0 +1,70 @@
+.wizard {
+}
+
+.steps {
+       background-image: none;
+       min-height: 520px;
+}
+
+#restore-browser {
+       width: 100%;
+       height: 100%;
+}
+
+#restore-browser-files {
+       width: 315px;
+       height: 434px;
+}
+
+#restore-browser-files, #restore-browser-versions, #restore-browser-selected {
+       overflow: auto;
+       width: 474px;
+       background-color: white;
+       border-radius: 4px;
+       padding: 4px;
+       -moz-user-select: -moz-none;
+       -webkit-user-select: none;
+       -ms-user-select: none;
+}
+
+#restore-browser-files {
+       height: 506px;
+}
+
+#restore-browser-versions {
+       height: 206px;
+}
+
+#restore-browser-selected {
+       height: 282px;
+}
+
+#restore-browser-files-loading {
+       position: absolute;
+       z-index: 10;
+       width: 482px;
+       height: 514px;
+       border-radius: 4px;
+       background: rgba(182,182,182,0.7) url('progress.gif') no-repeat center center;
+}
+
+.restore-browser-element-size {
+       float: right;
+       width: 100px;
+}
+
+.restore-browser-element-size {
+       line-height: 24px;
+}
+
+.file-browser-watermark {
+       padding: 77px 10px;
+       text-align: center;
+       font-size: 21px;
+       color: rgb(185, 184, 184);
+}
+
+#backup-to-restore-field > div {
+       height: 467px;
+       overflow-y: auto;
+}