mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-20 03:16:12 +00:00
System: Configuration: History - refactor using MVC components.
When \Deciso\OPNcentral\Central exists, there might be multiple providers to select from, so we can easily reuse the same component in both versions. closes https://github.com/opnsense/core/issues/6828
This commit is contained in:
parent
d9fcc0bbbf
commit
f75ec9688a
4
plist
4
plist
@ -269,10 +269,12 @@
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/CaptivePortal/SessionController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/CaptivePortal/VoucherController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/CaptivePortal/forms/dialogZone.xml
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/BackupController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/FirmwareController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/MenuController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/ServiceController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/BackupController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/FirmwareController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/HaltController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/IndexController.php
|
||||
@ -729,6 +731,7 @@
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/clients.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/index.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/vouchers.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Core/backup_history.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Core/firmware.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Core/halt.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Core/license.volt
|
||||
@ -1983,7 +1986,6 @@
|
||||
/usr/local/www/csrf.inc
|
||||
/usr/local/www/diag_authentication.php
|
||||
/usr/local/www/diag_backup.php
|
||||
/usr/local/www/diag_confbak.php
|
||||
/usr/local/www/diag_defaults.php
|
||||
/usr/local/www/diag_logs_settings.php
|
||||
/usr/local/www/fbegin.inc
|
||||
|
||||
@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Deciso B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
namespace OPNsense\Core\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\Core\ACL;
|
||||
use OPNsense\Core\Backend;
|
||||
use OPNsense\Core\Config;
|
||||
|
||||
|
||||
/**
|
||||
* Class BackupController
|
||||
* @package OPNsense\Core\Api
|
||||
*/
|
||||
class BackupController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* when the user-config-readonly privilege is set, raise an error
|
||||
*/
|
||||
private function throwReadOnly()
|
||||
{
|
||||
if ((new ACL())->hasPrivilege($this->getUserName(), 'user-config-readonly')) {
|
||||
throw new UserException(
|
||||
sprintf("User %s denied for write access (user-config-readonly set)", $this->getUserName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return available providers and their backup locations
|
||||
* @return array
|
||||
*/
|
||||
private function providers()
|
||||
{
|
||||
$result = [];
|
||||
$result['this'] = ['description' => gettext('This Firewall'), 'dirname' => '/conf/backup'];
|
||||
if (class_exists('\Deciso\OPNcentral\Central')) {
|
||||
$central = new \Deciso\OPNcentral\Central();
|
||||
$central->setUserScope($this->getUserName());
|
||||
$ctrHosts = [];
|
||||
foreach ($central->hosts->host->getNodes() as $itemid => $item) {
|
||||
$ctrHosts[$itemid] = ['description' => $item['description']];
|
||||
}
|
||||
foreach (glob('/conf/remote.backups/*') as $filename) {
|
||||
$dirname = basename($filename);
|
||||
if (isset($ctrHosts[$dirname])) {
|
||||
$result[$dirname] = $ctrHosts[$dirname];
|
||||
$result[$dirname]['dirname'] = $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* list available providers
|
||||
* @return array
|
||||
*/
|
||||
public function providersAction()
|
||||
{
|
||||
return ['items' => $this->providers()];
|
||||
}
|
||||
|
||||
/**
|
||||
* list available backups for selected host
|
||||
*/
|
||||
public function backupsAction($host)
|
||||
{
|
||||
$result = ['items' => []];
|
||||
$providers = $this->providers();
|
||||
if (!empty($providers[$host])) {
|
||||
foreach (glob($providers[$host]['dirname'] . "/config-*.xml") as $filename) {
|
||||
$xmlNode = @simplexml_load_file($filename, "SimpleXMLElement", LIBXML_NOERROR | LIBXML_ERR_NONE);
|
||||
if (isset($xmlNode->revision)) {
|
||||
$cfg_item = [
|
||||
'time' => (string)$xmlNode->revision->time,
|
||||
'time_iso' => date('c', (string)$xmlNode->revision->time),
|
||||
'description' => (string)$xmlNode->revision->description,
|
||||
'username' => (string)$xmlNode->revision->username,
|
||||
'filesize' => filesize($filename),
|
||||
'id' => basename($filename)
|
||||
] ;
|
||||
$result['items'][] = $cfg_item;
|
||||
}
|
||||
}
|
||||
// sort newest first
|
||||
usort($result['items'], function ($item1, $item2) {
|
||||
return $item1['time'] < $item2['time'];
|
||||
});
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* diff two backups for selected host
|
||||
*/
|
||||
public function diffAction($host, $backup1, $backup2)
|
||||
{
|
||||
$result = ['items' => []];
|
||||
$providers = $this->providers();
|
||||
if (!empty($providers[$host])) {
|
||||
$bckfilename1 = null;
|
||||
$bckfilename2 = null;
|
||||
foreach (glob($providers[$host]['dirname'] . "/config-*.xml") as $filename) {
|
||||
$bckfilename = basename($filename);
|
||||
if ($backup1 == $bckfilename) {
|
||||
$bckfilename1 = $filename;
|
||||
} elseif ($backup2 == $bckfilename) {
|
||||
$bckfilename2 = $filename;
|
||||
}
|
||||
}
|
||||
if (!empty($bckfilename1) && !empty($bckfilename2)) {
|
||||
$diff = [];
|
||||
exec("/usr/bin/diff -u " . escapeshellarg($bckfilename1) . " " . escapeshellarg($bckfilename2), $diff);
|
||||
if (!empty($diff)) {
|
||||
foreach ($diff as $line) {
|
||||
$result['items'][] = htmlspecialchars($line, ENT_QUOTES | ENT_HTML401);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete local backup
|
||||
*/
|
||||
public function deleteBackupAction($backup)
|
||||
{
|
||||
$this->throwReadOnly();
|
||||
foreach (glob("/conf/backup/config-*.xml") as $filename) {
|
||||
$bckfilename = basename($filename);
|
||||
if ($backup === $bckfilename) {
|
||||
@unlink($filename);
|
||||
return ['status' => 'deleted'];
|
||||
}
|
||||
}
|
||||
return ['status' => 'not_found'];
|
||||
}
|
||||
|
||||
/**
|
||||
* revert to local backup from history
|
||||
*/
|
||||
public function revertBackupAction($backup)
|
||||
{
|
||||
$this->throwReadOnly();
|
||||
foreach (glob("/conf/backup/config-*.xml") as $filename) {
|
||||
$bckfilename = basename($filename);
|
||||
if ($backup === $bckfilename) {
|
||||
$cnf = Config::getInstance();
|
||||
$cnf->backup();
|
||||
$cnf->restoreBackup($filename);
|
||||
$cnf->save();
|
||||
return ['status' => 'reverted'];
|
||||
}
|
||||
}
|
||||
return ['status' => 'not_found'];
|
||||
}
|
||||
|
||||
/**
|
||||
* download specified backup
|
||||
*/
|
||||
public function downloadAction($host, $backup)
|
||||
{
|
||||
$providers = $this->providers();
|
||||
if (!empty($providers[$host])) {
|
||||
foreach (glob($providers[$host]['dirname'] . "/config-*.xml") as $filename) {
|
||||
if ($backup == basename($filename)) {
|
||||
$payload = @simplexml_load_file($filename);
|
||||
$hostname = '';
|
||||
if ($payload !== false && isset($payload->system) && isset($payload->system->hostname)) {
|
||||
$hostname = $payload->system->hostname . "." .$payload->system->domain;
|
||||
}
|
||||
$target_filename = urlencode('config-' . $hostname . '-' . explode('/config-', $filename, 2)[1]);
|
||||
$this->response->setContentType('application/octet-stream');
|
||||
$this->response->setRawHeader("Content-Disposition: attachment; filename=" . $target_filename);
|
||||
$this->response->setRawHeader("Content-length: " . filesize($filename));
|
||||
$this->response->setRawHeader("Pragma: no-cache");
|
||||
$this->response->setRawHeader("Expires: 0");
|
||||
ob_clean();
|
||||
flush();
|
||||
readfile($filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2023 Deciso B.V.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OPNsense\Core;
|
||||
|
||||
/**
|
||||
* Class BackupController
|
||||
* @package OPNsense\Core
|
||||
*/
|
||||
class BackupController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function historyAction($selected_host=null)
|
||||
{
|
||||
$this->view->selected_host = $selected_host;
|
||||
$this->view->pick('OPNsense/Core/backup_history');
|
||||
}
|
||||
}
|
||||
@ -80,7 +80,8 @@
|
||||
<page-diagnostics-configurationhistory>
|
||||
<name>Diagnostics: Configuration History</name>
|
||||
<patterns>
|
||||
<pattern>diag_confbak.php*</pattern>
|
||||
<pattern>ui/core/backup/history*</pattern>
|
||||
<pattern>api/core/backup/*</pattern>
|
||||
</patterns>
|
||||
</page-diagnostics-configurationhistory>
|
||||
<page-diagnostics-factorydefaults>
|
||||
|
||||
@ -34,9 +34,7 @@
|
||||
<Configuration cssClass="fa fa-history fa-fw">
|
||||
<Backups url="/diag_backup.php"/>
|
||||
<Defaults url="/diag_defaults.php"/>
|
||||
<History url="/diag_confbak.php">
|
||||
<none url="/diag_confbak.php*" visibility="hidden"/>
|
||||
</History>
|
||||
<History url="/ui/core/backup/history/this"/>
|
||||
</Configuration>
|
||||
<Firmware cssClass="fa fa-building-o fa-fw">
|
||||
<Status order="10" url="/ui/core/firmware#status"/>
|
||||
|
||||
268
src/opnsense/mvc/app/views/OPNsense/Core/backup_history.volt
Normal file
268
src/opnsense/mvc/app/views/OPNsense/Core/backup_history.volt
Normal file
@ -0,0 +1,268 @@
|
||||
{#
|
||||
# Copyright (c) 2023 Deciso B.V.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#}
|
||||
|
||||
<style>
|
||||
.diff_record {
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
'use strict';
|
||||
function get_backups(){
|
||||
let provider = $("#providers").val();
|
||||
$("#host_processing").show();
|
||||
ajaxGet('/api/core/backup/backups/' + provider, {}, function(data, status){
|
||||
let target1 = $("#backups1").empty();
|
||||
let target2 = $("#backups2").empty();
|
||||
if (data.items && Object.keys(data.items).length > 1) {
|
||||
Object.keys(data.items).forEach(function(key) {
|
||||
let record = data.items[key];
|
||||
let payload = $("<div>");
|
||||
payload.append(record.time_iso, " ", record.username, "<br/>");
|
||||
payload.append($("<small/>").text(record.description));
|
||||
target1.append($("<option/>").attr('value', record.id).attr('data-content', payload.html()));
|
||||
target2.append($("<option/>").attr('value', record.id).attr('data-content', payload.html()));
|
||||
});
|
||||
}
|
||||
$(".backups").selectpicker('refresh');
|
||||
$("#backups1").change();
|
||||
$("#host_processing").hide();
|
||||
if ($("#backups1 option").length === 0) {
|
||||
$("#diff_tfoot").show();
|
||||
} else {
|
||||
$("#diff_tfoot").hide();
|
||||
}
|
||||
});
|
||||
if (provider === 'this' ) {
|
||||
$(".only_local").show();
|
||||
} else {
|
||||
$(".only_local").hide();
|
||||
}
|
||||
}
|
||||
$( document ).ready(function () {
|
||||
ajaxGet('/api/core/backup/providers', {}, function(data, status){
|
||||
let selected_host = "{{selected_host}}";
|
||||
if (data.items && Object.keys(data.items).length > 1) {
|
||||
let target = $("#providers").empty();
|
||||
Object.keys(data.items).forEach(function(key) {
|
||||
let opt = $("<option/>").attr('value', key).text(data.items[key].description);
|
||||
if (selected_host == key) {
|
||||
opt.attr('selected', 'selected');
|
||||
}
|
||||
target.append(opt);
|
||||
});
|
||||
target.selectpicker('refresh');
|
||||
}
|
||||
$("#providers").change(get_backups);
|
||||
$("#providers").change();
|
||||
if ($("#providers > option").length > 1) {
|
||||
$("#providers_pane").show();
|
||||
}
|
||||
});
|
||||
|
||||
$("#backups1").change(function(){
|
||||
let target = $("#backups2");
|
||||
target.val($("#backups1").val());
|
||||
if ($('#backups2 option').length > target[0].selectedIndex) {
|
||||
target[0].selectedIndex = target[0].selectedIndex + 1;
|
||||
}
|
||||
target.selectpicker('refresh');
|
||||
target.change();
|
||||
});
|
||||
|
||||
$("#backups2").change(function(){
|
||||
let provider = $("#providers").val();
|
||||
let url = '/api/core/backup/diff/'+provider+'/'+$("#backups1").val()+'/'+$("#backups2").val();
|
||||
ajaxGet(url, {}, function(data, status){
|
||||
let target = $("#diff_records").empty();
|
||||
if (data.items && data.items.length > 1) {
|
||||
for (let i=0 ; i < data.items.length ; ++i) {
|
||||
let $tr = $("<tr/>");
|
||||
let $td = $("<td class='diff_record'/>").append($("<div>").html(data.items[i]).text());
|
||||
let color = '#000000';
|
||||
switch (data.items[i][0]) {
|
||||
case '+':
|
||||
color = '#3bbb33';
|
||||
break;
|
||||
case '-':
|
||||
color = '#c13928';
|
||||
break;
|
||||
case '@':
|
||||
color = '#3bb9c3';
|
||||
break;
|
||||
}
|
||||
$td.css('color', color);
|
||||
target.append($tr.append($td));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#act_revert").click(function(event){
|
||||
event.preventDefault();
|
||||
if (!$("#backups1").val()) {
|
||||
return;
|
||||
}
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_INFO,
|
||||
title: "{{ lang._('Action') }}",
|
||||
message: "{{ lang._('Restore from Configuration Backup') }} <br/>" + $("#backups1 option:selected").data('content'),
|
||||
buttons: [{
|
||||
label: "{{ lang._('No') }}",
|
||||
action: function(dialogRef) {
|
||||
dialogRef.close();
|
||||
}}, {
|
||||
label: "{{ lang._('Yes') }}",
|
||||
action: function(dialogRef) {
|
||||
ajaxCall("/api/core/backup/revert_backup/" + $("#backups1").val(),{}, function(){
|
||||
dialogRef.close();
|
||||
get_backups();
|
||||
});
|
||||
}
|
||||
}]
|
||||
});
|
||||
});
|
||||
$("#act_remove").click(function(event){
|
||||
event.preventDefault();
|
||||
if (!$("#backups1").val()) {
|
||||
return;
|
||||
}
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_DANGER,
|
||||
title: "{{ lang._('Action') }}",
|
||||
message: "{{ lang._('Remove Configuration Backup') }} <br/>" + $("#backups1 option:selected").data('content'),
|
||||
buttons: [{
|
||||
label: "{{ lang._('No') }}",
|
||||
action: function(dialogRef) {
|
||||
dialogRef.close();
|
||||
}}, {
|
||||
label: "{{ lang._('Yes') }}",
|
||||
action: function(dialogRef) {
|
||||
ajaxCall("/api/core/backup/delete_backup/" + $("#backups1").val(),{}, function(){
|
||||
dialogRef.close();
|
||||
});
|
||||
}
|
||||
}]
|
||||
});
|
||||
});
|
||||
$("#act_download").click(function(event){
|
||||
event.preventDefault();
|
||||
if (!$("#backups1").val()) {
|
||||
return;
|
||||
}
|
||||
let url = '/api/core/backup/download/' + $("#providers").val() + '/' + $("#backups1").val();
|
||||
let link = document.createElement("a");
|
||||
$(link).click(function(e) {
|
||||
e.preventDefault();
|
||||
window.location.href = url;
|
||||
});
|
||||
$(link).click();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div class="tab-content content-box __mb" id="providers_pane" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ lang._('Host')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<select id="providers" class="selectpicker">
|
||||
<option value="this" selected>{{ lang._('This Firewall')}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content content-box __mb">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{ lang._('Backups (compare)')}}
|
||||
<i id="host_processing" class="fa fa-fw fa-spinner fa-pulse" style="display: none;"></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="height: 70px;">
|
||||
<td>
|
||||
<select class="selectpicker backups" id="backups1"></select>
|
||||
<select class="selectpicker backups" id="backups2"></select>
|
||||
<div>
|
||||
<a id="act_revert" class="only_local btn btn-default btn-xs" data-toggle="tooltip" title="{{ lang._('Revert to this configuration')}}">
|
||||
<i class="fa fa-sign-in fa-fw"></i>
|
||||
</a>
|
||||
<a id="act_remove" class="only_local btn btn-default btn-xs" data-toggle="tooltip" title="{{ lang._('Remove this backup')}}">
|
||||
<i class="fa fa-trash fa-fw"></i>
|
||||
</a>
|
||||
<a id="act_download" class="btn btn-default btn-xs" data-toggle="tooltip" title="{{ lang._('Download this backup')}}">
|
||||
<i class="fa fa-download fa-fw"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content content-box __mb">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th >{{ lang._('Changes between selected versions')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="diff_records">
|
||||
</tbody>
|
||||
<tfoot id="diff_tfoot" style="display: none;">
|
||||
<tr>
|
||||
<td>{{ lang._('No backups available')}}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,325 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014 Deciso B.V.
|
||||
* Copyright (C) 2005 Colin Smith <ethethlay@gmail.com>
|
||||
* Copyright (C) 2010 Jim Pingle <jimp@pfsense.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
require_once("guiconfig.inc");
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$pconfig['backupcount'] = isset($config['system']['backupcount']) ? $config['system']['backupcount'] : null;
|
||||
|
||||
$cnf = OPNsense\Core\Config::getInstance();
|
||||
$confvers = $cnf->getBackups(true);
|
||||
array_shift($confvers);
|
||||
|
||||
if (!empty($_GET['getcfg'])) {
|
||||
foreach ($confvers as $filename => $revision) {
|
||||
if ($revision['time'] == $_GET['getcfg']) {
|
||||
$exp_name = urlencode("config-{$config['system']['hostname']}.{$config['system']['domain']}-{$_GET['getcfg']}.xml");
|
||||
$exp_data = file_get_contents($filename);
|
||||
$exp_size = strlen($exp_data);
|
||||
|
||||
header("Content-Type: application/octet-stream");
|
||||
header("Content-Disposition: attachment; filename={$exp_name}");
|
||||
header("Content-Length: $exp_size");
|
||||
echo $exp_data;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oldfile = '';
|
||||
$newfile = '';
|
||||
$diff = '';
|
||||
|
||||
if (!empty($_GET['diff']) && isset($_GET['oldtime']) && isset($_GET['newtime'])
|
||||
&& is_numeric($_GET['oldtime']) && (is_numeric($_GET['newtime']) || ($_GET['newtime'] == 'current'))) {
|
||||
foreach ($confvers as $filename => $revision) {
|
||||
if ($revision['time'] == $_GET['oldtime']) {
|
||||
$oldfile = $filename;
|
||||
}
|
||||
if ($revision['time'] == $_GET['newtime']) {
|
||||
$newfile = $filename;
|
||||
}
|
||||
}
|
||||
|
||||
$oldtime = $_GET['oldtime'];
|
||||
$oldcheck = $oldtime;
|
||||
|
||||
if ($_GET['newtime'] == 'current') {
|
||||
$newfile = '/conf/config.xml';
|
||||
$newtime = $config['revision']['time'];
|
||||
} else {
|
||||
$newtime = $_GET['newtime'];
|
||||
$newcheck = $newtime;
|
||||
}
|
||||
} elseif (count($confvers)) {
|
||||
$files = array_keys($confvers);
|
||||
$newfile = '/conf/config.xml';
|
||||
$newtime = $config['revision']['time'];
|
||||
$oldfile = $files[0];
|
||||
$oldtime = $confvers[$oldfile]['time'];
|
||||
}
|
||||
|
||||
if (file_exists($oldfile) && file_exists($newfile)) {
|
||||
exec("/usr/bin/diff -u " . escapeshellarg($oldfile) . " " . escapeshellarg($newfile), $diff);
|
||||
}
|
||||
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$input_errors = array();
|
||||
$pconfig = $_POST;
|
||||
|
||||
$cnf = OPNsense\Core\Config::getInstance();
|
||||
$confvers = $cnf->getBackups(true);
|
||||
array_shift($confvers);
|
||||
|
||||
$user = getUserEntry($_SESSION['Username']);
|
||||
$readonly = userHasPrivilege($user, 'user-config-readonly');
|
||||
|
||||
if (!empty($_POST['act']) && $_POST['act'] == 'revert') {
|
||||
foreach ($confvers as $filename => $revision) {
|
||||
if (isset($revision['time']) && $revision['time'] == $_POST['time']) {
|
||||
if (!$readonly && config_restore($filename) == 0) {
|
||||
$savemsg = sprintf(gettext('Successfully reverted to timestamp %s with description "%s".'), date(gettext("n/j/y H:i:s"), $revision['time']), $revision['description']);
|
||||
} else {
|
||||
$savemsg = gettext("Unable to revert to the selected configuration.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif (!empty($_POST['act']) && $_POST['act'] == "delete") {
|
||||
foreach ($confvers as $filename => $revision) {
|
||||
if (isset($revision['time']) && $revision['time'] == $_POST['time']) {
|
||||
if (!$readonly && file_exists($filename)) {
|
||||
$savemsg = sprintf(gettext('Deleted backup with timestamp %s and description "%s".'), date(gettext("n/j/y H:i:s"), $revision['time']), $revision['description']);
|
||||
unset($confvers[$filename]);
|
||||
@unlink($filename);
|
||||
} else {
|
||||
$savemsg = gettext("Unable to delete the selected configuration.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include("head.inc");
|
||||
?>
|
||||
|
||||
<script>
|
||||
//<![CDATA[
|
||||
$(document).ready(function () {
|
||||
// revert config dialog
|
||||
$(".act_revert").click(function () {
|
||||
var id = $(this).data('id');
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_INFO,
|
||||
title: "<?= html_safe(gettext('Action')) ?>",
|
||||
message: "<?= html_safe(gettext('Restore from Configuration Backup')) ?> <br/> <?= html_safe(gettext('Version')) ?>: " + id,
|
||||
buttons: [{
|
||||
label: "<?= html_safe(gettext('No')) ?>",
|
||||
action: function (dialogRef) {
|
||||
dialogRef.close();
|
||||
}
|
||||
}, {
|
||||
label: "<?= html_safe(gettext('Yes')) ?>",
|
||||
action: function (dialogRef) {
|
||||
$("#time").val(id);
|
||||
$("#action").val("revert");
|
||||
$("#iform").submit()
|
||||
}
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
// delete backup dialog
|
||||
$(".act_delete").click(function () {
|
||||
var id = $(this).data('id');
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_DANGER,
|
||||
title: "<?= html_safe(gettext('Action')) ?>",
|
||||
message: "<?= html_safe(gettext('Remove Configuration Backup')) ?> <br/> <?= html_safe(gettext('Version')) ?>: " + id,
|
||||
buttons: [{
|
||||
label: "<?= html_safe(gettext('No')) ?>",
|
||||
action: function (dialogRef) {
|
||||
dialogRef.close();
|
||||
}
|
||||
}, {
|
||||
label: "<?= html_safe(gettext('Yes')) ?>",
|
||||
action: function (dialogRef) {
|
||||
$("#time").val(id);
|
||||
$("#action").val("delete");
|
||||
$("#iform").submit()
|
||||
}
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<body>
|
||||
|
||||
<?php
|
||||
|
||||
include("fbegin.inc");
|
||||
|
||||
?>
|
||||
<section class="page-content-main">
|
||||
<form method="post" id="iform">
|
||||
<input type="hidden" id="time" name="time" value="" />
|
||||
<input type="hidden" id="action" name="act" value="" />
|
||||
</form>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<?php if (isset($input_errors) && count($input_errors) > 0) print_input_errors($input_errors); ?>
|
||||
<?php if ($savemsg) print_info_box($savemsg); ?>
|
||||
<section class="col-xs-12">
|
||||
<?php if ($diff): ?>
|
||||
<div class="content-box tab-content table-responsive __mb" style="overflow: scroll;">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<strong><?= sprintf(
|
||||
gettext('Configuration diff from %s to %s'),
|
||||
date(gettext('n/j/y H:i:s'), $oldtime),
|
||||
date(gettext('n/j/y H:i:s'), $newtime)
|
||||
) ?></strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<?php
|
||||
foreach ($diff as $line):
|
||||
switch (substr($line, 0, 1)) {
|
||||
case '+':
|
||||
$color = '#3bbb33';
|
||||
break;
|
||||
case '-':
|
||||
$color = '#c13928';
|
||||
break;
|
||||
case '@':
|
||||
$color = '#3bb9c3';
|
||||
break;
|
||||
default:
|
||||
$color = '#000000';
|
||||
} ?>
|
||||
<span style="color: <?= $color; ?>; white-space: pre-wrap; font-family: monospace;"><?= html_safe($line) ?></span>
|
||||
<br/>
|
||||
<?php endforeach; ?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php if (count($confvers)): ?>
|
||||
<form method="get">
|
||||
<div class="content-box tab-content table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2"><strong><?= gettext('History') ?></strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button type="submit" name="diff" class="btn btn-primary pull-left" value="Diff">
|
||||
<?= gettext('View differences'); ?>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<?= gettext("To view the differences between an older configuration and a newer configuration, select the older configuration using the left column of radio options and select the newer configuration in the right column, then press the button."); ?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th colspan="2"><?= gettext("Diff"); ?></th>
|
||||
<th><?= gettext("Date"); ?></th>
|
||||
<th><?= gettext("Size"); ?></th>
|
||||
<th><?= gettext("Configuration Change"); ?></th>
|
||||
<th class="text-nowrap"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<input type="radio" name="newtime" value="current" <?= !isset($newcheck) || $newcheck == 'current' ? 'checked="checked"' : '' ?>/>
|
||||
</td>
|
||||
<td><?= date(gettext("n/j/y H:i:s"), $config['revision']['time']) ?></td>
|
||||
<td><?= format_bytes(filesize("/conf/config.xml")) ?></td>
|
||||
<td><?= html_safe($config['revision']['username'])?>: <?=html_safe($config['revision']['description']); ?></td>
|
||||
<td class="text-nowrap"><strong><?= gettext("Current"); ?></strong></td>
|
||||
</tr>
|
||||
<?php $last = count($confvers); $curr = 1; foreach ($confvers as $version): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="radio" name="oldtime"
|
||||
value="<?= $version['time']; ?>" <?= (!isset($oldcheck) && $curr == 1) || (isset($oldcheck) && $oldcheck == $version['time']) ? 'checked="checked"' : '' ?>/>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($curr != $last): ?>
|
||||
<input type="radio" name="newtime" value="<?= $version['time']; ?>" <?= isset($newcheck) && $newcheck == $version['time'] ? 'checked="checked"' : '' ?>/>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td><?= date(gettext("n/j/y H:i:s"), $version['time']) ?></td>
|
||||
<td><?= format_bytes($version['filesize']) ?></td>
|
||||
<td><?= html_safe($version['username']);?>: <?=html_safe($version['description']);?></td>
|
||||
<td class="text-nowrap">
|
||||
<a data-id="<?= $version['time']; ?>" href="#"
|
||||
class="act_revert btn btn-default btn-xs" data-toggle="tooltip"
|
||||
title="<?= html_safe(gettext('Revert to this configuration')) ?>">
|
||||
<i class="fa fa-sign-in fa-fw"></i>
|
||||
</a>
|
||||
<a data-id="<?= $version['time']; ?>" href="#"
|
||||
class="act_delete btn btn-default btn-xs" data-toggle="tooltip"
|
||||
title="<?= html_safe(gettext('Remove this backup')) ?>">
|
||||
<i class="fa fa-trash fa-fw"></i>
|
||||
</a>
|
||||
<a href="diag_confbak.php?getcfg=<?= $version['time']; ?>"
|
||||
class="btn btn-default btn-xs" data-toggle="tooltip"
|
||||
title="<?= html_safe(gettext('Download this backup')) ?>">
|
||||
<i class="fa fa-download fa-fw"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php $curr++; endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif ?>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
|
||||
include 'foot.inc';
|
||||
Loading…
x
Reference in New Issue
Block a user