* Bacula(R) - The Network Backup Solution
* Baculum - Bacula web interface
*
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
*
* The main author of Baculum is Marcin Haba.
* The original author of Bacula is Kern Sibbald, with contributions
*/
class BClientScript extends TClientScript {
- const SCRIPTS_VERSION = 5;
+ const SCRIPTS_VERSION = 6;
public function getScriptUrl()
{
}
function update_job_table(table_obj, new_data) {
+
+ var current_page = table_obj.page();
+
var rows = table_obj.rows();
var old_jobs = {};
table_obj.data().toArray().forEach(function(job) {
for (var jobid in job_add_mod) {
table_obj.row.add(job_add_mod[jobid]).draw();
}
+
+ table_obj.page(current_page).draw(false);
}
/**
return $.fn.dataTable.util.escapeRegex(value);
};
+
+function get_table_toolbar(table, actions, txt) {
+ var table_toolbar = document.querySelector('div.table_toolbar');
+ table_toolbar.className += ' dt-buttons';
+ table_toolbar.style.display = 'none';
+ var title = document.createTextNode(txt.actions);
+ var select = document.createElement('SELECT');
+ var option = document.createElement('OPTION');
+ option.value = '';
+ select.appendChild(option);
+ var acts = {};
+ for (var i = 0; i < actions.length; i++) {
+ option = document.createElement('OPTION');
+ option.value = actions[i].action,
+ label = document.createTextNode(actions[i].label);
+ option.appendChild(label);
+ select.appendChild(option);
+ acts[actions[i].action] = actions[i];
+ }
+ var btn = document.createElement('BUTTON');
+ btn.type = 'button';
+ btn.className = 'dt-button';
+ btn.style.verticalAlign = 'top';
+ label = document.createTextNode(txt.ok);
+ btn.appendChild(label);
+ btn.addEventListener('click', function(e) {
+ if (!select.value) {
+ // no value, no action
+ return
+ }
+ var selected = [];
+ var sel_data = table.rows({selected: true}).data();
+ sel_data.each(function(v, k) {
+ selected.push(v[acts[select.value].value]);
+ });
+
+ // call validation if defined
+ if (acts[select.value].hasOwnProperty('validate') && typeof(acts[select.value].validate) == 'function') {
+ if (acts[select.value].validate(sel_data) === false) {
+ // validation error
+ return false;
+ }
+ }
+ // call pre-action before calling bulk action
+ if (acts[select.value].hasOwnProperty('before') && typeof(acts[select.value].before) == 'function') {
+ acts[select.value].before();
+ }
+ selected = selected.join('|');
+ acts[select.value].callback.options.RequestTimeOut = 60000; // Timeout set to 1 minute
+ acts[select.value].callback.setCallbackParameter(selected);
+ acts[select.value].callback.dispatch();
+ });
+ table_toolbar.appendChild(title);
+ table_toolbar.appendChild(select);
+ table_toolbar.appendChild(btn);
+ return table_toolbar;
+}
+
$(function() {
W3SideBar.init();
set_sbbr_compatibility();
msgid "Cache retention"
msgstr "Cache retention"
+
+msgid "Bulk action"
+msgstr "Bulk action"
+
+msgid "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again."
+msgstr "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again."
+
+msgid "Refresh page"
+msgstr "Refresh page"
msgid "Cache retention"
msgstr "Cache retention"
+
+msgid "Bulk action"
+msgstr "Bulk action"
+
+msgid "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again."
+msgstr "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again."
+
+msgid "Refresh page"
+msgstr "Refresh page"
msgid "Cache retention"
msgstr "Retencja pamięci podręcznej"
+msgid "Bulk action"
+msgstr "Akcja zbiorowa"
+
+msgid "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again."
+msgstr "Następujące zadania są uruchomione i nie mogą zostać usunięte: %running_jobs Aby je usunąć, proszę je zatrzymać i spróbować ponownie."
+
+msgid "Refresh page"
+msgstr "Odśwież stronę"
msgid "Cache retention"
msgstr "Retenção de cache"
+msgid "Bulk action"
+msgstr "Bulk action"
+
+msgid "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again."
+msgstr "Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again."
+
+msgid "Refresh page"
+msgstr "Refresh page"
<com:BClientScript ScriptUrl=<%~ ../JavaScript/datatables.js %> />
<com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.responsive.js %> />
<com:BClientScript ScriptUrl=<%~ ../JavaScript/responsive.jqueryui.js %> />
+ <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.buttons.js %> />
+ <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.html5.js %> />
+ <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.colVis.js %> />
+ <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.select.js %> />
<com:BClientScript ScriptUrl=<%~ ../JavaScript/excanvas.js %> />
<com:BClientScript ScriptUrl=<%~ ../JavaScript/bacula-config.js %> />
<com:BClientScript ScriptUrl=<%~ ../JavaScript/misc.js %> />
<com:BClientScript ScriptUrl=<%~ ../JavaScript/graph.js %> />
<com:BClientScript ScriptUrl=<%~ ../JavaScript/statistics.js %> />
- <com:BClientScript ScriptUrl=<%~ ../JavaScript/dataTables.buttons.js %> />
- <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.html5.js %> />
- <com:BClientScript ScriptUrl=<%~ ../JavaScript/buttons.colVis.js %> />
<!-- Top container -->
<div class="w3-bar w3-top w3-black w3-large" style="z-index:4">
<button type="button" class="w3-bar-item w3-button w3-hover-none w3-hover-text-light-grey" onclick="W3SideBar.open();"><i class="fa fa-bars"></i> Menu</button>
document.location.href = '<%=$this->Service->constructUrl('JobHistoryList')%>';
}
var oJobList = {
+ table: null,
+ table_toolbar: null,
+ actions: [
+ {
+ action: 'cancel',
+ label: '<%[ Cancel ]%>',
+ value: 'jobid',
+ callback: <%=$this->CancelJobsAction->ActiveControl->Javascript%>,
+ before: function() {
+ oBulkActionsModal.show_output(true);
+ }
+ },
+ {
+ action: 'delete',
+ label: '<%[ Delete ]%>',
+ value: 'jobid',
+ callback: <%=$this->DeleteJobsAction->ActiveControl->Javascript%>,
+ before: function() {
+ oBulkActionsModal.show_output(true);
+ },
+ validate: function(selected) {
+ var running_jobs = [];
+ selected.each(function(v, k) {
+ if (JobStatus.is_running(v.jobstatus)) {
+ running_jobs.push(' - [' + v.jobid + '] ' + v.name);
+ }
+ });
+ if (running_jobs.length > 0) {
+ var emsg = '<%[ Following jobs are running and cannot be deleted: %running_jobs To delete them, please stop these jobs and try again. ]%>';
+ emsg = emsg.replace('%running_jobs', '<hr />' + running_jobs.join('<br />') + '<hr />');
+ oBulkActionsModal.set_error(emsg);
+ return false;
+ }
+ return true;
+ }
+ }
+ ],
ids: {
job_list: 'job_list',
job_list_body: 'job_list_body'
update_job_table(this.table, this.data);
} else {
this.set_table();
+ this.set_bulk_actions();
+ this.set_events();
}
},
+ set_events: function() {
+ document.getElementById(this.ids.job_list).addEventListener('click', function(e) {
+ $(function() {
+ this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none';
+ }.bind(this));
+ }.bind(this));
+ },
set_table: function() {
this.table = $('#' + this.ids.job_list).DataTable({
data: this.data,
deferRender: true,
- dom: 'lBfrtip',
+ dom: 'lB<"table_toolbar">frtip',
stateSave: true,
buttons: [
'copy', 'csv', 'colvis'
className: "dt-body-right",
targets: [ 15, 16 ]
}],
+ select: {
+ style: 'os',
+ selector: 'td:not(:last-child):not(:first-child)',
+ blurable: false
+ },
order: [1, 'desc'],
initComplete: function () {
this.api().columns([2, 3, 4, 5, 14]).every(function () {
});
}
});
+ },
+ set_bulk_actions: function() {
+ this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+ actions: '<%[ Actions ]%>',
+ ok: '<%[ OK ]%>'
+ });
}
};
MonitorParams = {jobs: null};
$(function() {
MonitorCallsInterval.push(function() { oJobList.init(); });
});
+
+/**
+ * Defne bulk actions output id here because expression tags (< % = % >) cannot
+ * be defined in the TCallback ClientSide properties.
+ */
+var bulk_actions_output_id = '<%=$this->SourceTemplateControl->BulkActions->BulkActionsOutput->ClientID%>';
</script>
+<com:TCallback ID="CancelJobsAction" OnCallback="cancelJobs">
+ <prop:ClientSide.OnLoading>
+ oBulkActionsModal.clear_output(bulk_actions_output_id);
+ oBulkActionsModal.show_loader(true);
+ </prop:ClientSide.OnLoading>
+ <prop:ClientSide.OnComplete>
+ oBulkActionsModal.show_loader(false);
+ // job status in the Catalog changes a little moment after finishing cancel command
+ setTimeout('oMonitor()', 5000);
+ </prop:ClientSide.OnComplete>
+</com:TCallback>
+<com:TCallback ID="DeleteJobsAction" OnCallback="deleteJobs">
+ <prop:ClientSide.OnLoading>
+ oBulkActionsModal.clear_output(bulk_actions_output_id);
+ oBulkActionsModal.show_loader(true);
+ </prop:ClientSide.OnLoading>
+ <prop:ClientSide.OnComplete>
+ oBulkActionsModal.show_loader(false);
+ oJobList.table_toolbar.style.display = 'none';
+ oMonitor();
+ </prop:ClientSide.OnComplete>
+</com:TCallback>
+<com:Application.Web.Portlets.BulkActionsModal ID="BulkActions" />
</com:TContent>
* Bacula(R) - The Network Backup Solution
* Baculum - Bacula web interface
*
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
*
* The main author of Baculum is Marcin Haba.
* The original author of Bacula is Kern Sibbald, with contributions
);
$this->getPage()->getCallbackClient()->callClientFunction('refresh_job_history');
}
+
+ /**
+ * Cancel multiple jobs.
+ * Used for bulk actions.
+ *
+ * @param TCallback $sender callback object
+ * @param TCallbackEventPrameter $param event parameter
+ * @return none
+ */
+ public function cancelJobs($sender, $param) {
+ $result = [];
+ $jobids = explode('|', $param->getCallbackParameter());
+ for ($i = 0; $i < count($jobids); $i++) {
+ $ret = $this->getModule('api')->set(
+ ['jobs', intval($jobids[$i]), 'cancel']
+ );
+ if ($ret->error !== 0) {
+ $result[] = $ret->output;
+ break;
+ }
+ $result[] = implode(PHP_EOL, $ret->output);
+ }
+ $this->getCallbackClient()->update($this->BulkActions->BulkActionsOutput, implode(PHP_EOL, $result));
+ }
+
+ /**
+ * Delete multiple jobs.
+ * Used for bulk actions.
+ *
+ * @param TCallback $sender callback object
+ * @param TCallbackEventPrameter $param event parameter
+ * @return none
+ */
+ public function deleteJobs($sender, $param) {
+ $result = [];
+ $jobids = explode('|', $param->getCallbackParameter());
+ for ($i = 0; $i < count($jobids); $i++) {
+ $ret = $this->getModule('api')->remove(
+ ['jobs', intval($jobids[$i])]
+ );
+ if ($ret->error !== 0) {
+ $result[] = $ret->output;
+ break;
+ }
+ $result[] = implode(PHP_EOL, $ret->output);
+ }
+ $this->getCallbackClient()->update($this->BulkActions->BulkActionsOutput, implode(PHP_EOL, $result));
+ }
}
</div>
<script type="text/javascript">
var oVolumeList = {
+ table: null,
+ table_toolbar: null,
+ actions: [
+ {
+ action: 'prune',
+ label: '<%[ Prune ]%>',
+ value: 'mediaid',
+ callback: <%=$this->PruneVolumesAction->ActiveControl->Javascript%>,
+ before: function() {
+ oBulkActionsModal.show_output(true);
+ }
+ },
+ {
+ action: 'purge',
+ label: '<%[ Purge ]%>',
+ value: 'mediaid',
+ callback: <%=$this->PurgeVolumesAction->ActiveControl->Javascript%>,
+ before: function() {
+ oBulkActionsModal.show_output(true);
+ }
+ }
+ ],
ids: {
volume_list: 'volume_list',
volume_list_body: 'volume_list_body'
},
init: function() {
this.set_table();
+ this.set_bulk_actions();
+ this.set_events();
+ },
+ set_events: function() {
+ document.getElementById(this.ids.volume_list).addEventListener('click', function(e) {
+ $(function() {
+ this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none';
+ }.bind(this));
+ }.bind(this));
},
set_table: function() {
- var table = $('#' + this.ids.volume_list).DataTable({
+ this.table = $('#' + this.ids.volume_list).DataTable({
data: <%=json_encode($this->volumes)%>,
deferRender: true,
- dom: 'lBfrtip',
+ dom: 'lB<"table_toolbar">frtip',
stateSave: true,
buttons: [
'copy', 'csv', 'colvis'
className: "dt-body-right",
targets: [ 18 ]
}],
+ select: {
+ style: 'os',
+ selector: 'td:not(:last-child):not(:first-child)',
+ blurable: false
+ },
order: [3, 'asc'],
initComplete: function () {
this.api().columns([2, 3, 6, 10, 13]).every(function () {
});
}
});
+ },
+ set_bulk_actions: function() {
+ this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+ actions: '<%[ Actions ]%>',
+ ok: '<%[ OK ]%>'
+ });
}
};
oVolumeList.init();
+/**
+ * Defne bulk actions output id here because expression tags (< % = % >) cannot
+ * be defined in the TCallback ClientSide properties.
+ */
+var bulk_actions_output_id = '<%=$this->SourceTemplateControl->BulkActions->BulkActionsOutput->ClientID%>';
</script>
+<com:TCallback ID="PruneVolumesAction" OnCallback="pruneVolumes">
+ <prop:ClientSide.OnLoading>
+ oBulkActionsModal.clear_output(bulk_actions_output_id);
+ oBulkActionsModal.show_loader(true);
+ </prop:ClientSide.OnLoading>
+ <prop:ClientSide.OnComplete>
+ oBulkActionsModal.show_loader(false);
+ </prop:ClientSide.OnComplete>
+</com:TCallback>
+<com:TCallback ID="PurgeVolumesAction" OnCallback="purgeVolumes">
+ <prop:ClientSide.OnLoading>
+ oBulkActionsModal.clear_output(bulk_actions_output_id);
+ oBulkActionsModal.show_loader(true);
+ </prop:ClientSide.OnLoading>
+ <prop:ClientSide.OnComplete>
+ oBulkActionsModal.show_loader(false);
+ </prop:ClientSide.OnComplete>
+</com:TCallback>
+<com:Application.Web.Portlets.BulkActionsModal
+ ID="BulkActions"
+ RefreshPageBtn="true"
+ />
</com:TContent>
* Bacula(R) - The Network Backup Solution
* Baculum - Bacula web interface
*
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
*
* The main author of Baculum is Marcin Haba.
* The original author of Bacula is Kern Sibbald, with contributions
self::USE_CACHE
)->output;
}
+
+ /**
+ * Prune multiple volumes.
+ * Used for bulk actions.
+ *
+ * @param TCallback $sender callback object
+ * @param TCallbackEventPrameter $param event parameter
+ * @return none
+ */
+ public function pruneVolumes($sender, $param) {
+ $result = [];
+ $mediaids = explode('|', $param->getCallbackParameter());
+ for ($i = 0; $i < count($mediaids); $i++) {
+ $ret = $this->getModule('api')->set(
+ ['volumes', intval($mediaids[$i]), 'prune']
+ );
+ if ($ret->error !== 0) {
+ $result[] = $ret->output;
+ break;
+ }
+ $result[] = implode(PHP_EOL, $ret->output);
+ }
+ $this->getCallbackClient()->update($this->BulkActions->BulkActionsOutput, implode(PHP_EOL, $result));
+ }
+
+ /**
+ * Purge multiple volumes.
+ * Used for bulk actions.
+ *
+ * @param TCallback $sender callback object
+ * @param TCallbackEventPrameter $param event parameter
+ * @return none
+ */
+ public function purgeVolumes($sender, $param) {
+ $result = [];
+ $mediaids = explode('|', $param->getCallbackParameter());
+ for ($i = 0; $i < count($mediaids); $i++) {
+ $ret = $this->getModule('api')->set(
+ ['volumes', intval($mediaids[$i]), 'purge']
+ );
+ if ($ret->error !== 0) {
+ $result[] = $ret->output;
+ break;
+ }
+ $result[] = implode(PHP_EOL, $ret->output);
+ }
+ $this->getCallbackClient()->update($this->BulkActions->BulkActionsOutput, implode(PHP_EOL, $result));
+ }
}
?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('System.Web.UI.ActiveControls.TActiveLabel');
+Prado::using('Application.Web.Portlets.Portlets');
+
+/**
+ * Bulk actions modal control.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Control
+ * @package Baculum Web
+ */
+class BulkActionsModal extends Portlets {
+
+ const REFRESH_PAGE_BTN = 'RefreshPageBtn';
+
+ public function onLoad($param) {
+ parent::onLoad($param);
+
+ if ($this->getRefreshPageBtn()) {
+ $this->getPage()->getCallbackClient()->show('bulk_actions_refresh_page');
+ }
+ }
+
+ public function setRefreshPageBtn($refresh_page_btn) {
+ $refresh_page_btn = TPropertyValue::ensureBoolean($refresh_page_btn);
+ $this->setViewState(self::REFRESH_PAGE_BTN, $refresh_page_btn);
+ }
+
+ public function getRefreshPageBtn() {
+ return $this->getViewState(self::REFRESH_PAGE_BTN);
+ }
+}
+?>
--- /dev/null
+<div id="bulk_actions_modal" class="w3-modal" style="display: none">
+ <div class="w3-modal-content w3-card-4 w3-animate-zoom" style="width: 830px">
+ <header class="w3-container w3-green">
+ <span onclick="oBulkActionsModal.show_output(false);" class="w3-button w3-display-topright">×</span>
+ <h2><%[ Bulk action ]%></h2>
+ </header>
+ <div class="w3-margin-left w3-margin-right" style="max-height: 400px; overflow-x: auto;">
+ <div class="w3-code">
+ <pre><com:TActiveLabel ID="BulkActionsOutput" /></pre>
+ </div>
+ </div>
+ <footer class="w3-container w3-center w3-border-top">
+ <button type="button" class="w3-button w3-section w3-green" onclick="oBulkActionsModal.show_output(false);"><i class="fa fa-check"></i> <%[ OK ]%></button>
+ <button id="bulk_actions_refresh_page" type="button" class="w3-button w3-section w3-green" style="display: none" onclick="oBulkActionsModal.refresh_page();"><i class="fas fa-redo-alt"></i> <%[ Refresh page ]%></button>
+ <i id="bulk_actions_loader" class="fa fa-sync w3-spin w3-margin-left"></i>
+ </footer>
+ </div>
+</div>
+<div id="bulk_actions_validation_error_modal" class="w3-modal" style="display: none">
+ <div class="w3-modal-content w3-card-4 w3-animate-zoom" style="width: 600px">
+ <header class="w3-container w3-red">
+ <span onclick="oBulkActionsModal.show_error(false);" class="w3-button w3-display-topright">×</span>
+ <h2><%[ Validation error ]%></h2>
+ </header>
+ <div class="w3-margin-left w3-margin-right" style="max-height: 400px; overflow-x: auto;">
+ <p id="bulk_actions_validation_error_txt"></p>
+ </div>
+ <footer class="w3-container w3-center w3-border-top">
+ <button type="button" class="w3-button w3-section w3-green" onclick="oBulkActionsModal.show_error(false);"><i class="fas fa-check"></i> <%[ OK ]%></button>
+ </footer>
+ </div>
+</div>
+<script>
+var oBulkActionsModal = {
+ show_loader: function(show) {
+ document.getElementById('bulk_actions_loader').style.display = show ? '' : 'none';
+ },
+ clear_output: function(id) {
+ document.getElementById(id).textContent = '';
+ },
+ show_output: function(show) {
+ document.getElementById('bulk_actions_modal').style.display = show ? 'block' : '';
+ },
+ set_error: function(emsg) {
+ document.getElementById('bulk_actions_validation_error_txt').innerHTML = emsg;
+ this.show_error(true);
+ },
+ show_error: function(show) {
+ document.getElementById('bulk_actions_validation_error_modal').style.display = show ? 'block' : '';
+ },
+ refresh_page: function() {
+ document.location.reload();
+ },
+ show_refresh_btn: function(show) {
+ document.getElementById('bulk_actions_refresh_page').style.display = show ? 'block' : '';
+ },
+};
+</script>
margin-right: 5px;
}
+.dataTables_wrapper select {
+ padding: 0.5em;
+ border: 1px solid #999;
+ border-radius: 2px;
+ background-color: #e9e9e9;
+ background-image: linear-gradient(to bottom, white 0%, #e9e9e9 100%);
+}
+
+div.table_toolbar {
+ display: inline-block;
+ margin-left: 10px;
+}
+
+@media screen and (max-width: 640px) {
+ div.table_toolbar {
+ display: block !important;
+ text-align: center;
+ }
+}
+
+div.table_toolbar select {
+ font-weight: bold;
+ margin-left: 0.333em;
+ margin-right: 2px;
+}
+
+table.dataTable tbody > tr.selected, table.dataTable tbody > tr > .selected {
+ background-color: #f5a55b !important;
+}
+
+.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover {
+ background-color: #ffcf9e !important;
+}
+
th.action_col, td.action_col {
width: 110px !important;
}