Merge pull request #2254 from fraenki/haproxy_300a

net/haproxy: finishing touches for release 3.0
This commit is contained in:
Frank Wall
2021-03-05 00:10:58 +01:00
committed by GitHub
13 changed files with 390 additions and 46 deletions
+7 -2
View File
@@ -9,7 +9,8 @@ Plugin Changelog
3.0
Added:
* new feature to change server state and weight on-the-fly (#2213)
* add new maintenance page to change server state and weight on-the-fly (#2213)
* add new commands to update SSL certificates in runtime (#2244, #1882)
* add new SSL bind option: prefer-client-ciphers
* add global option to enable old buggy behaviour for PROXY v2 connections
* add support for HTTP/2 in health checks
@@ -22,11 +23,12 @@ Added:
* add support for server templates (#1975)
* add support for additional resolver options (#1975)
* add support for resolve-prefer option (#1975)
* add pre-defined cron jobs to maintenance page
Fixed:
* fix maintenance page (python error: 'list' object has no attribute 'strip')
* prevent service outage by aborting "Apply" when configtest fails
* fix direct links to individual statistics tabs
* prevent the deletion of items that are still referenced elsewhere (core/#1897)
Changed:
* change default SSL version to TLSv1.2 (ssl-min-ver)
@@ -40,6 +42,9 @@ Changed:
* make restart/reload commands usable in cron jobs
* relax GUI input validation for servers, move validation to jinja template (#1975)
Deprecated:
* nbproc is deprecated and will be removed in os-haproxy 4.0
2.26
Fixed:
@@ -31,16 +31,21 @@
namespace OPNsense\HAProxy\Api;
use OPNsense\Base\ApiControllerBase;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
use OPNsense\Cron\Cron;
use OPNsense\HAProxy\HAProxy;
/**
* Class MaintenanceController
* @package OPNsense\HAProxy
*/
class MaintenanceController extends ApiControllerBase
class MaintenanceController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'haproxy';
protected static $internalModelClass = '\OPNsense\HAProxy\HAProxy';
/**
* jQuery bootstrap certificates diff list
* @return array|mixed
@@ -268,4 +273,99 @@ class MaintenanceController extends ApiControllerBase
];
}
/**
* create new cron job or return already available one
* @return array status action
*/
public function fetchCronIntegrationAction()
{
$result = array("result" => "no change");
if ($this->request->isPost()) {
$mdlHaproxy = $this->getModel();
$backend = new Backend();
// Define possible cron jobs with their configd actions
$cronjobs = array(
'syncCerts' => 'cert_sync_bulk',
'updateOcsp' => 'update_ocsp',
'reloadService' => 'reload',
'restartService' => 'restart',
);
// Iterate over all possible cron jobs
foreach ($cronjobs as $cron => $cron_action) {
// Name of the item that holds the cron UUID
$cron_ref = "${cron}Cron";
// Check if the cron job is enabled or disabled
if ((string)$mdlHaproxy->maintenance->cronjobs->$cron == "1") {
// Check if a cron job already exists
if ((string)$mdlHaproxy->maintenance->cronjobs->$cron_ref == "") {
// Create new cron job
$mdlCron = new Cron();
// NOTE: Only configd actions are valid commands for cronjobs
// and they *must* provide a description that is not empty.
$cron_uuid = $mdlCron->newDailyJob(
"HAProxy",
"haproxy ${cron_action}",
"Added by HAProxy plugin",
"*",
"1"
);
$mdlHaproxy->maintenance->cronjobs->$cron_ref = $cron_uuid;
// Save updated configuration.
if ($mdlCron->performValidation()->count() == 0) {
$mdlCron->serializeToConfig();
// save data to config, do not validate because the current in memory model doesn't know about the
// cron item just created.
$mdlHaproxy->serializeToConfig($validateFullModel = false, $disable_validation = true);
Config::getInstance()->save();
// Refresh the crontab
$backend->configdRun('template reload OPNsense/Cron');
// (res)start daemon
$backend->configdRun("cron restart");
$this->getLogger()->error("HAProxy: successfully created cron job $cron ($cron_uuid)");
$result['result'] = "new";
$result['uuid'] = $cron_uuid;
} else {
$this->getLogger()->error("HAProxy: unable to create cron job $cron");
$result['result'] = "unable to add cron";
}
}
} else {
// Check if a cron job exists
if ((string)$mdlHaproxy->maintenance->cronjobs->$cron_ref != "") {
// Clean existin entry
$cron_uuid = (string)$mdlHaproxy->maintenance->cronjobs->$cron_ref;
$mdlHaproxy->maintenance->cronjobs->$cron_ref = "";
// Delete the cronjob item
$mdlCron = new Cron();
if ($mdlCron->jobs->job->del($cron_uuid)) {
// If item is removed, serialize to config and save
$mdlCron->serializeToConfig();
$mdlHaproxy->serializeToConfig($validateFullModel = false, $disable_validation = true);
Config::getInstance()->save();
// Regenerate the crontab
$backend->configdRun('template reload OPNsense/Cron');
// (res)start daemon
$backend->configdRun("cron restart");
$this->getLogger()->error("HAProxy: successfully deleted cron job $cron ($cron_uuid)");
$result['result'] = "deleted";
} else {
$this->getLogger()->error("HAProxy: unable to delete cron job $cron ($cron_uuid)");
$result['result'] = "unable to delete cron";
}
}
}
}
}
return $result;
}
}
@@ -1,7 +1,7 @@
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2016-2021 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
@@ -44,6 +44,7 @@ class SettingsController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'haproxy';
protected static $internalModelClass = '\OPNsense\HAProxy\HAProxy';
protected static $internalModelUseSafeDelete = true;
public function getFrontendAction($uuid = null)
{
@@ -45,7 +45,7 @@ class StatisticsController extends ApiControllerBase
* get info
* @return array|mixed
*/
public function infoAction($zoneid = 0)
public function infoAction()
{
$backend = new Backend();
$responseRaw = $backend->configdRun("haproxy statistics info");
@@ -57,7 +57,7 @@ class StatisticsController extends ApiControllerBase
* get counters
* @return array|mixed
*/
public function countersAction($zoneid = 0)
public function countersAction()
{
$backend = new Backend();
$responseRaw = $backend->configdRun("haproxy statistics stat");
@@ -69,7 +69,7 @@ class StatisticsController extends ApiControllerBase
* get tables
* @return array|mixed
*/
public function tablesAction($zoneid = 0)
public function tablesAction()
{
$backend = new Backend();
$responseRaw = $backend->configdRun("haproxy statistics table");
@@ -41,5 +41,6 @@ class MaintenanceController extends \OPNsense\Base\IndexController
{
// choose template
$this->view->pick('OPNsense/HAProxy/maintenance');
$this->view->maintenanceCronjobsForm = $this->getForm("maintenanceCronjobs");
}
}
@@ -7,14 +7,14 @@
<id>haproxy.general.tuning.root</id>
<label>Run as root</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable HAProxy running as root.<br/><div class="text-info"><b>NOTE:</b> Enabling root could be a security issue but it's required by some feature.</div>]]></help>
<help><![CDATA[Enable or disable HAProxy running as user root. Enabling this option is strongly discouraged.<br/><div class="text-info"><b>NOTE:</b> Running as user root could be a security issue but it may be required by some features.</div>]]></help>
<advanced>true</advanced>
</field>
<field>
<id>haproxy.general.tuning.nbproc</id>
<label>HAProxy processes</label>
<label>HAProxy processes (DEPRECATED)</label>
<type>text</type>
<help><![CDATA[Number of HAProxy processes to start.<br/><div class="text-info"><b>NOTE:</b> You may experience random issues in multi-process mode. For more information about the "nbproc" option please see the HAProxy Documentation.</div>]]></help>
<help><![CDATA[Number of HAProxy processes to start.<br/><div class="text-info"><b>WARNING:</b> This option is deprecated and will be removed in a future version of HAProxy, threads should be used instead.</div>]]></help>
<advanced>true</advanced>
</field>
<field>
@@ -0,0 +1,46 @@
<form>
<field>
<label>Sync SSL certificate changes</label>
<type>header</type>
<style>table_cron table_cron_syncCerts</style>
</field>
<field>
<id>haproxy.maintenance.cronjobs.syncCerts</id>
<label>Enable</label>
<type>checkbox</type>
<help><![CDATA[Periodically sync SSL certificate changes into the running HAProxy service. This is most useful when using short-lived Let's Encrypt certificates, but changes to other certificates will also be synced. Note that when using the Let's Encrypt plugin, it is also possible to use an <a target="_blank" href="/ui/acmeclient/actions">Automation</a> instead of this cron job.]]></help>
</field>
<field>
<label>Update OCSP data for SSL certificates</label>
<type>header</type>
<style>table_cron table_cron_updateOcsp</style>
</field>
<field>
<id>haproxy.maintenance.cronjobs.updateOcsp</id>
<label>Enable</label>
<type>checkbox</type>
<help><![CDATA[Periodically fetch OCSP data for all configured SSL certificates. Note that OCSP support needs to be enabled in <a target="_blank" href="/ui/haproxy#general-settings">HAProxy service settings</a>.]]></help>
</field>
<field>
<label>Reload HAProxy service</label>
<type>header</type>
<style>table_cron table_cron_reloadService</style>
</field>
<field>
<id>haproxy.maintenance.cronjobs.reloadService</id>
<label>Enable</label>
<type>checkbox</type>
<help><![CDATA[Periodically perform a reload of the HAProxy service. This may cause a minor service disruption, depending on the configuration. A nightly reload could be used to apply configuration changes outside of business hours, or to workaround a bug.]]></help>
</field>
<field>
<label>Restart HAProxy service</label>
<type>header</type>
<style>table_cron table_cron_restartService</style>
</field>
<field>
<id>haproxy.maintenance.cronjobs.restartService</id>
<label>Enable</label>
<type>checkbox</type>
<help><![CDATA[Periodically perform a full restart of the HAProxy service. This will cause a notable service disruption. A full restart is required in some cases where a reload does not work as expected (i.e. due to long-running connections and very high timeout values).]]></help>
</field>
</form>
@@ -2815,5 +2815,81 @@
</hostname>
</mailer>
</mailers>
<maintenance>
<cronjobs>
<syncCerts type="BooleanField">
<default>0</default>
<Required>N</Required>
</syncCerts>
<syncCertsCron type="ModelRelationField">
<Model>
<queues>
<source>OPNsense.Cron.Cron</source>
<items>jobs.job</items>
<display>description</display>
<filters>
<origin>/HAProxy/</origin>
</filters>
</queues>
</Model>
<ValidationMessage>Related cron not found.</ValidationMessage>
<Required>N</Required>
</syncCertsCron>
<updateOcsp type="BooleanField">
<default>0</default>
<Required>N</Required>
</updateOcsp>
<updateOcspCron type="ModelRelationField">
<Model>
<queues>
<source>OPNsense.Cron.Cron</source>
<items>jobs.job</items>
<display>description</display>
<filters>
<origin>/HAProxy/</origin>
</filters>
</queues>
</Model>
<ValidationMessage>Related cron not found.</ValidationMessage>
<Required>N</Required>
</updateOcspCron>
<reloadService type="BooleanField">
<default>0</default>
<Required>N</Required>
</reloadService>
<reloadServiceCron type="ModelRelationField">
<Model>
<queues>
<source>OPNsense.Cron.Cron</source>
<items>jobs.job</items>
<display>description</display>
<filters>
<origin>/HAProxy/</origin>
</filters>
</queues>
</Model>
<ValidationMessage>Related cron not found.</ValidationMessage>
<Required>N</Required>
</reloadServiceCron>
<restartService type="BooleanField">
<default>0</default>
<Required>N</Required>
</restartService>
<restartServiceCron type="ModelRelationField">
<Model>
<queues>
<source>OPNsense.Cron.Cron</source>
<items>jobs.job</items>
<display>description</display>
<filters>
<origin>/HAProxy/</origin>
</filters>
</queues>
</Model>
<ValidationMessage>Related cron not found.</ValidationMessage>
<Required>N</Required>
</restartServiceCron>
</cronjobs>
</maintenance>
</items>
</model>
@@ -1,6 +1,6 @@
{#
Copyright (C) 2016-2017 Frank Wall
Copyright (C) 2016-2021 Frank Wall
OPNsense® is Copyright © 2014 2015 by Deciso B.V.
All rights reserved.
@@ -28,6 +28,72 @@ POSSIBILITY OF SUCH DAMAGE.
#}
<script>
$( document ).ready(function() {
// Get cronjobs
var cronjobs_data_get_map = {'frm_cronjobs':"/api/haproxy/maintenance/get"};
// load initial data
mapDataToFormUI(cronjobs_data_get_map).done(function(data){
// Add link to cron job edit page: First iterate over all cron settings.
// FIXME: Oh boy, this is ugly. Should be refactored.
$.each(data.frm_cronjobs.haproxy.maintenance.cronjobs, function(key, value) {
// Check if cron setting is enabled.
if (value == 1) {
// Find the matching cron job reference.
cron_cfg = key + 'Cron';
$.each(data.frm_cronjobs.haproxy.maintenance.cronjobs, function(cronkey, cronvalue) {
// Check if it is the correct entry for this cron setting.
if (cronkey == cron_cfg) {
// Get the cron job UUID.
$.each(cronvalue, function(refkey, refvalue) {
// Only the "selected" item belongs to this entry.
if (refvalue.selected == 1) {
// Find the correct container for this cron setting.
content_id = "[id=\"haproxy.maintenance.cronjobs." + key + "\"]";
$(content_id).each(function(){
// Finally add the link to the cron job edit page.
cron_link = "<br><a href=\"/ui/cron/item/open/" + refkey + "\"><span class=\"fa fa-pencil\"></span> {{ lang._('Configure cron job') }}</a>";
$(this).closest("td").append(cron_link);
});
};
});
};
});
};
});
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
});
// Save & reconfigure cron to activate changes
$('[id*="saveAndReconfigureAct"]').each(function(){
$(this).click(function(){
// set progress animation
$('[id*="saveAndReconfigureAct_progress"]').each(function(){
$(this).addClass("fa fa-spinner fa-pulse");
});
// extract the form id from the button id
var frm_id = "frm_" + $(this).attr("id").split('_')[1]
// save data for this tab
saveFormToEndpoint(url="/api/haproxy/maintenance/set",formid=frm_id,callback_ok=function(){
// Handle cron integration
ajaxCall(url="/api/haproxy/maintenance/fetchCronIntegration", sendData={}, callback=function(data,status) {
});
// when done, disable progress animation
$('[id*="saveAndReconfigureAct_progress"]').each(function(){
$(this).removeClass("fa fa-spinner fa-pulse");
// reload page to show or hide links to cron edit page
setTimeout(function () {
window.location.reload(true)
}, 300);
});
});
});
});
// grid-certificates
function syncErrorMessage(modified, deleted) {
message = ``;
@@ -64,7 +130,7 @@ POSSIBILITY OF SUCH DAMAGE.
$.post('/api/haproxy/maintenance/certDiff', payload, function(data) {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_INFO,
title: "{{ lang._('Diff between configured and active ssl certificates') }}",
title: "{{ lang._('Diff between configured and active SSL certificates') }}",
message: `<pre>${data}</pre>`,
buttons: [{
label: '{{ lang._('Close') }}',
@@ -80,7 +146,7 @@ POSSIBILITY OF SUCH DAMAGE.
$.post('/api/haproxy/maintenance/certActions', payload, function(data_actions) {
question = ''
question += `<pre>${data_actions}</pre>`;
question += '<b>{{ lang._('Apply ssl certificates to HaProxy?') }}</b></br></br>';
question += '<b>{{ lang._('Apply SSL certificates to HAProxy?') }}</b></br></br>';
stdDialogConfirm('{{ lang._('Confirmation Required') }}',
question,
@@ -92,7 +158,7 @@ POSSIBILITY OF SUCH DAMAGE.
var error_msg = syncErrorMessage(data.result.modified, data.result.deleted);
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('Error applying ssl certificates to HAProxy') }}",
title: "{{ lang._('Error applying SSL certificates to HAProxy') }}",
message: error_msg,
buttons: [{
label: '{{ lang._('Close') }}',
@@ -124,8 +190,8 @@ POSSIBILITY OF SUCH DAMAGE.
formatters: {
"commands": function (column, row) {
buttons = ""
buttons += "<button type=\"button\" data-action=\"showDiff\" title=\"{{ lang._('Show diff between configured ssl certificates and certificates from HAProxy memory.') }}\" class=\"btn btn-xs btn-default\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-info-circle\"></span></button>"
buttons += " <button type=\"button\" data-action=\"applyDiff\" title=\"{{ lang._('Apply diff and sync certificates into HAProxy memory.') }}\" class=\"btn btn-xs btn-default\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-refresh\"></span></button>"
buttons += "<button type=\"button\" data-action=\"showDiff\" title=\"{{ lang._('Show diff') }}\" class=\"btn btn-xs btn-default\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-info-circle\"></span></button>"
buttons += " <button type=\"button\" data-action=\"applyDiff\" title=\"{{ lang._('Apply changes') }}\" class=\"btn btn-xs btn-default\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-refresh\"></span></button>"
return buttons;
},
},
@@ -202,7 +268,7 @@ POSSIBILITY OF SUCH DAMAGE.
var error_msg = syncErrorMessage(data.result.modified, data.result.deleted);
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('Error applying ssl certificates to HAProxy') }}",
title: "{{ lang._('Error applying SSL certificates to HAProxy') }}",
message: error_msg,
buttons: [{
label: '{{ lang._('Close') }}',
@@ -234,10 +300,10 @@ POSSIBILITY OF SUCH DAMAGE.
formatters: {
"commands": function (column, row) {
buttons = ""
buttons += "<button type=\"button\" title=\"{{ lang._('Set administrative state to ready. Puts the server in normal mode.') }}\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"ready\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-check\"></span></button>"
buttons += " <button type=\"button\" title=\"{{ lang._('Set administrative state to drain. Removes the server from load balancing but still allows it to be health checked and to accept new persistent connections') }}\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"drain\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-sort-amount-desc\"></span></button>"
buttons += " <button type=\"button\" title=\"{{ lang._('Set administrative state to maintenance. Disables any traffic to the server as well as any health checks.') }}\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"maint\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-wrench\"></span></button>"
buttons += " <button type=\"button\" title=\"{{ lang._('Change server weight.') }}\" class=\"btn btn-xs btn-default command-set-weight\" data-weight=\"" + row.weight + "\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-balance-scale\"></span></button>"
buttons += "<button type=\"button\" title=\"{{ lang._('Set state to ready') }}\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"ready\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-check\"></span></button>"
buttons += " <button type=\"button\" title=\"{{ lang._('Set state to drain') }}\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"drain\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-sort-amount-desc\"></span></button>"
buttons += " <button type=\"button\" title=\"{{ lang._('Set state to maintenance') }}\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"maint\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-wrench\"></span></button>"
buttons += " <button type=\"button\" title=\"{{ lang._('Change server weight') }}\" class=\"btn btn-xs btn-default command-set-weight\" data-weight=\"" + row.weight + "\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-balance-scale\"></span></button>"
return buttons;
},
},
@@ -344,7 +410,7 @@ POSSIBILITY OF SUCH DAMAGE.
});
question += '</ul>';
question += '<b>{{ lang._('State: ') }}' + state + '</b></br></br>';
question += '{{ lang._('Set administrative state for all selected server?') }} </br></br>';
question += '{{ lang._('Set administrative state for all selected servers?') }} </br></br>';
stdDialogConfirm('{{ lang._('Confirmation Required') }}',
question,
@@ -389,7 +455,7 @@ POSSIBILITY OF SUCH DAMAGE.
question += '<div class="form-group" style="display: block;">';
question += '<input class="form-control" id="newBulkWeight" value="" type="text"/>';
question += '</div>';
question += '{{ lang._('Set weight for all selected server?') }} </br></br>';
question += '{{ lang._('Set weight for all selected servers?') }} </br></br>';
stdDialogConfirm('{{ lang._('Confirmation Required') }}',
question,
@@ -425,12 +491,24 @@ POSSIBILITY OF SUCH DAMAGE.
});
});
// update history on tab state and implement navigation
if(window.location.hash != "") {
$('a[href="' + window.location.hash + '"]').click()
}
$('.nav-tabs a').on('shown.bs.tab', function (e) {
history.pushState(null, null, e.target.hash);
});
$(window).on('hashchange', function(e) {
$('a[href="' + window.location.hash + '"]').click()
});
});
</script>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
<li class="active"><a data-toggle="tab" href="#server"><b>{{ lang._('Server') }}</b></a></li>
<li class="active"><a data-toggle="tab" href="#server"><b>{{ lang._('Servers') }}</b></a></li>
<li><a data-toggle="tab" href="#ssl-certs"><b>{{ lang._('SSL Certificates') }}</b></a></li>
<li><a data-toggle="tab" href="#cronjobs"><b>{{ lang._('Cron Jobs') }}</b></a></li>
</ul>
<div class="content-box tab-content">
@@ -440,11 +518,11 @@ POSSIBILITY OF SUCH DAMAGE.
<thead>
<tr>
<th data-column-id="id" data-type="string" data-identifier="true" data-visible="false">{{ lang._('id') }}</th>
<th data-column-id="pxname" data-type="string">{{ lang._('Proxy') }}</th>
<th data-column-id="svname" data-type="string">{{ lang._('Server') }}</th>
<th data-column-id="pxname" data-width="9em" data-type="string">{{ lang._('Virtual Service') }}</th>
<th data-column-id="svname" data-width="9em" data-type="string">{{ lang._('Real Server') }}</th>
<th data-column-id="addr" data-type="string">{{ lang._('Address') }}</th>
<th data-column-id="status" data-type="string">{{ lang._('Status') }}</th>
<th data-column-id="check_status" data-type="string">{{ lang._('Check Status') }}</th>
<th data-column-id="check_status" data-width="8em" data-type="string">{{ lang._('Check Status') }}</th>
<th data-column-id="weight" data-type="string">{{ lang._('Weight') }}</th>
<th data-column-id="scur" data-type="string">{{ lang._('Sessions') }}</th>
<th data-column-id="bin" data-type="string">{{ lang._('Bytes in') }}</th>
@@ -461,14 +539,24 @@ POSSIBILITY OF SUCH DAMAGE.
<tr>
<td></td>
<td>
<button data-action="setStateBulk" title="{{ lang._('Set administrative state to ready for all selected items.') }}" data-state="ready" type="button" class="btn btn-xs btn-default"><span class="fa fa-check"></span></button>
<button data-action="setStateBulk" title="{{ lang._('Set administrative state to drain for all selected items.') }}" data-state="drain" type="button" class="btn btn-xs btn-default"><span class="fa fa-sort-amount-desc"></span></button>
<button data-action="setStateBulk" title="{{ lang._('Set administrative state to maintenance for all selected items.') }}" data-state="maint" type="button" class="btn btn-xs btn-default"><span class="fa fa-wrench"></span></button>
<button data-action="setWeightBulk" title="{{ lang._('Change server weight for all selected items.') }}" data-weight="" type="button" class="btn btn-xs btn-default"><span class="fa fa-balance-scale"></span></button>
<button data-action="setStateBulk" title="{{ lang._('Set state to ready (bulk)') }}" data-state="ready" type="button" class="btn btn-xs btn-default"><span class="fa fa-check"></span></button>
<button data-action="setStateBulk" title="{{ lang._('Set state to drain (bulk)') }}" data-state="drain" type="button" class="btn btn-xs btn-default"><span class="fa fa-sort-amount-desc"></span></button>
<button data-action="setStateBulk" title="{{ lang._('Set state to maintenance (bulk)') }}" data-state="maint" type="button" class="btn btn-xs btn-default"><span class="fa fa-wrench"></span></button>
<button data-action="setWeightBulk" title="{{ lang._('Change server weight (bulk)') }}" data-weight="" type="button" class="btn btn-xs btn-default"><span class="fa fa-balance-scale"></span></button>
</td>
</tr>
</tfoot>
</table>
<div class="col-md-12">
<p>{{ lang._("%sChoose a command to change a server's state in runtime:%s") | format('<b>', '</b>') }}</p>
<ul>
<li><span class="fa fa-check"></span> {{ lang._('%sSet state to ready:%s This puts the server in normal mode.') | format('<b>', '</b>') }}</li>
<li><span class="fa fa-sort-amount-desc"></span> {{ lang._('%sSet state to drain:%s This removes the server from load balancing. Health checks will continue to run and it still accepts new persistent connections.') | format('<b>', '</b>') }}</li>
<li><span class="fa fa-wrench"></span> {{ lang._('%sSet state to maintenance:%s This disables any traffic to the server. Health checks will also be disabled.') | format('<b>', '</b>') }}</li>
<li><span class="fa fa-balance-scale"></span> {{ lang._("%sChange server weight:%s Adjust the server's weight relative to other servers. Servers will receive a load proportional to their weight.") | format('<b>', '</b>') }}</li>
</ul>
<p>{{ lang._('%sNOTE:%s These changes will not be persisted across restarts of HAProxy.') | format('<b>', '</b>') }}</p>
</div>
</div>
<div id="ssl-certs" class="tab-pane fade in">
@@ -490,8 +578,8 @@ POSSIBILITY OF SUCH DAMAGE.
<tr>
<td></td>
<td>
<button data-action="showDiffBulk" title="{{ lang._('Show diff between configured ssl certificates and certificates from HAProxy memory for selected frontends.') }}" type="button" class="btn btn-xs btn-default"><span class="fa fa-info-circle"></span></button>
<button data-action="applyDiffBulk" title="{{ lang._('Apply diff and sync certificates into HAProxy memory for selected frontends.') }}" type="button" class="btn btn-xs btn-default"><span class="fa fa-refresh"></span></button>
<button data-action="showDiffBulk" title="{{ lang._('Show diff (bulk)') }}" type="button" class="btn btn-xs btn-default"><span class="fa fa-info-circle"></span></button>
<button data-action="applyDiffBulk" title="{{ lang._('Apply changes (bulk)') }}" type="button" class="btn btn-xs btn-default"><span class="fa fa-refresh"></span></button>
</td>
</tr>
</tfoot>
@@ -502,6 +590,29 @@ POSSIBILITY OF SUCH DAMAGE.
<br/>
<br/>
</div>
<div class="col-md-12">
<p>{{ lang._("%sApply SSL certificate changes in runtime:%s") | format('<b>', '</b>') }}</p>
<ul>
<li><span class="fa fa-info-circle"></span> {{ lang._('%sShow diff:%s Show difference between configured SSL certificates and SSL certificates from the running HAProxy service.') | format('<b>', '</b>') }}</li>
<li><span class="fa fa-refresh"></span> {{ lang._('%sApply changes:%s Apply all changes by syncing all shown SSL certificates into running HAProxy service.') | format('<b>', '</b>') }}</li>
</ul>
<p>{{ lang._('%sNOTE:%s Changes can only be applied for Public Services that already exist in the running HAProxy service. When adding or removing Public Services HAProxy must be reloaded or restarted.') | format('<b>', '</b>') }}</p>
</div>
</div>
<div id="cronjobs" class="tab-pane fade in">
<div class="content-box" style="padding-bottom: 1.5em;">
{{ partial("layout_partials/base_form",['fields':maintenanceCronjobsForm,'id':'frm_cronjobs'])}}
<div class="col-md-12">
<hr />
<button class="btn btn-primary" id="saveAndReconfigureAct_cronjobs" type="button"><b>{{ lang._('Apply') }}</b> <i id="saveAndReconfigureAct_progress"></i></button>
</div>
<div class="col-md-12">
<br/>
{{ lang._('%sNOTE:%s When enabling multiple cron jobs, please adjust them so that they do not run at the same time. Check the %scron settings page%s for more cron job details and additional customization options.') | format('<b>', '</b>', '<a href="/ui/cron">', '</a>') }}
<br/>
</div>
</div>
</div>
</div>
@@ -2,7 +2,7 @@
<?php
/*
* Copyright (C) 2016-2018 Frank Wall
* Copyright (C) 2016-2021 Frank Wall
* Copyright (C) 2015 Deciso B.V.
* All rights reserved.
*
@@ -113,7 +113,7 @@ command:configctl template reload OPNsense/HAProxy 2 > /dev/null; /usr/local/opn
parameters:
type:script_output
message:Sync ssl certificates into HAProxy memory for all frontends
description:Sync ssl certificates changes into HAProxy memory
description:Sync SSL certificate changes into running HAProxy service
[showconf]
command:test -f /usr/local/etc/haproxy.conf.staging && cat /usr/local/etc/haproxy.conf.staging
@@ -836,8 +836,8 @@ userlist {{object.name | regex_replace ("[^A-Za-z0-9]","")}}
{#- ############################### -#}
global
{# # NOTE: Running as root could be a security issue, but is required for some features. #}
{% if OPNsense.HAProxy.general.tuning.root != "1" %}
# NOTE: Could be a security issue, but required for some feature.
uid 80
{% endif %}
gid 80
@@ -1106,11 +1106,11 @@ resolvers {{resolver.id}}
# Mailer: {{mailer.name}}
mailers {{mailer.id}}
timeout mail {{mailer.timeout}}s
{% if mailer.mailservers|default("") != "" %}
{% for mailserver in mailer.mailservers.split(",") %}
{% if mailer.mailservers|default("") != "" %}
{% for mailserver in mailer.mailservers.split(",") %}
mailer {{mailserver}} {{mailserver}}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% else %}
# NOTE: Mailer {{mailer.name}} ignored: not configured in any backend
@@ -1518,10 +1518,10 @@ backend {{backend.name}}
{% else %}
{# # server type #}
{% set server_basics = [] %}
{% if server_data.type|default("") == 'static' %}
{% do server_basics.append('server ' ~ server_data.name ~ ' ' ~ server_data.address) %}
{% else %}
{% if server_data.type|default("") == 'template' %}
{% do server_basics.append('server-template ' ~ server_data.name ~ ' ' ~ server_data.number ~ ' ' ~ server_data.serviceName) %}
{% else %}
{% do server_basics.append('server ' ~ server_data.name ~ ' ' ~ server_data.address) %}
{% endif %}
{# # collect optional server parameters #}
{% set server_options = [] %}
@@ -1627,11 +1627,15 @@ backend {{backend.name}}
{% if backend.linkedResolver|default("") != "" %}
{# # prefer backend configuration #}
{% set resolver_id = backend.linkedResolver %}
{% set resolver_opts = backend.resolverOpts %}
{% if backend.resolverOpts|default("") != "" %}
{% set resolver_opts = backend.resolverOpts %}
{% endif %}
{% elif server_data.linkedResolver|default("") != "" and server_data.type|default("") == 'template' %}
{# # use resolver for server template #}
{% set resolver_id = server_data.linkedResolver %}
{% set resolver_opts = server_data.resolverOpts %}
{% if server_data.resolverOpts|default("") != "" %}
{% set resolver_opts = server_data.resolverOpts %}
{% endif %}
{% endif %}
{% if resolver_id != '' %}
{% set resolver_data = helpers.getUUID(resolver_id) %}