firmware: epic battle to use form template in settings

There can only be one...
This commit is contained in:
Franco Fichtner
2026-09-02 13:21:36 +02:00
parent 6264c38120
commit 1ee6520a1f
6 changed files with 185 additions and 3 deletions
+2
View File
@@ -222,6 +222,7 @@
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/ServiceController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/SnapshotsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/TunablesController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/firmware.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/hasyncSettings.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/snapshot.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/tunable.xml
@@ -695,6 +696,7 @@
/usr/local/opnsense/mvc/app/models/OPNsense/Core/ACL.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Core/FieldTypes/FirmwareField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/FieldTypes/TunableField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Firmware.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Firmware.xml
@@ -41,6 +41,7 @@ class FirmwareController extends \OPNsense\Base\IndexController
*/
public function indexAction()
{
$this->view->settingsForm = $this->getForm('firmware');
$this->view->pick('OPNsense/Core/firmware');
}
}
@@ -0,0 +1,46 @@
<form>
<field>
<id>firmware.mirror</id>
<label>Mirror</label>
<type>dropdown</type>
<help>Select an alternate firmware mirror.</help>
</field>
<field>
<id>firmware.flavour</id>
<label>Flavour</label>
<type>dropdown</type>
<advanced>true</advanced>
<help>Select an alternate firmware flavour.</help>
</field>
<field>
<id>firmware.type</id>
<label>Type</label>
<type>dropdown</type>
<help>Select the release type.</help>
</field>
<field>
<id>firmware.reboot</id>
<label>Reboot</label>
<type>checkbox</type>
<advanced>true</advanced>
<help>Always reboot after a successful update.</help>
</field>
<field>
<id>firmware.aux</id>
<label>Repository</label>
<type>checkbox</type>
<advanced>true</advanced>
<help>activate the auxiliary packages repository.</help>
</field>
<field>
<id>firmware.usage</id>
<label>Usage</label>
<type>label</type>
</field>
<field>
<id>firmware.actions</id>
<type>buttons</type>
<parent>ConnectionDialog</parent>
<cancel>true</cancel>
</field>
</form>
@@ -0,0 +1,108 @@
<?php
/*
* Copyright (C) 2026 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\FieldTypes;
use OPNsense\Base\FieldTypes\BaseListField;
class FirmwareField extends BaseListField
{
/**
* @var string field contains the XML path to use
*/
protected $internalPath = 'mirrors.mirror';
/**
* set the path in the XML to read the values from
* @param $value string dotted path
*/
public function setPath($value)
{
$this->internalPath = $value;
}
/**
* @var string if custom input is allowed
*/
protected $internalCustom = false;
/**
* set to allow custom values in this field
* @param $value boolean value 0/1
*/
public function setCustom($value)
{
if (trim(strtoupper($value)) == 'Y') {
$this->internalCustom = true;
} else {
$this->internalCustom = false;
}
}
protected function actionPostLoadingEvent()
{
$new_list = [];
$key = $this->internalPath == 'mirrors.mirror' ? 'url' : 'name';
foreach (glob(__DIR__ . "/../repositories/*.xml") as $xml) {
$repositoryXml = simplexml_load_file($xml);
if ($repositoryXml === false || $repositoryXml->getName() != 'firmware') {
syslog(LOG_ERR, 'unable to parse firmware file ' . $xml);
} else {
$node = $repositoryXml;
foreach (explode('.', $this->internalPath) as $path) {
if (isset($node->$path)) {
$node = $node->$path;
} else {
$node = null;
break;
}
}
if (!is_null($node)) {
foreach ($node as $value) {
$new_list[(string)$value->$key] = (string)$value->description;
}
}
}
}
if ($this->internalCustom) {
$custom = $this->getValue();
if (!in_array($custom, $new_list)) {
$custom = '';
}
/* this construct is difficult as it also overwrites '(default)' */
$new_list[$custom] = '(custom)';
}
$this->internalOptionList = $this->setStaticOptions($new_list);
}
}
@@ -3,10 +3,17 @@
<version>1.0.1</version>
<description>Firmware settings</description>
<items>
<mirror type="TextField"/>
<flavour type="TextField"/>
<mirror type=".\FirmwareField">
<Custom>Y</Custom>
</mirror>
<flavour type=".\FirmwareField">
<Path>flavours.flavour</Path>
<Custom>Y</Custom>
</flavour>
<plugins type="TextField"/>
<type type="TextField"/>
<type type=".\FirmwareField">
<Path>families.family</Path>
</type>
<subscription type="TextField"/>
<reboot type="BooleanField"/>
<aux type="BooleanField"/>
@@ -633,6 +633,20 @@
}
$( document ).ready(function() {
var data_get_map = {'frm_FirmwareSettings':"/api/core/firmware/get"};
mapDataToFormUI(data_get_map).done(function(data) {
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
});
$("#reconfigureAct").SimpleActionButton({
onPreAction: function () {
const dfObj = new $.Deferred();
saveFormToEndpoint("/api/unbound/settings/set", 'frm_FirmwareSettings', function () { dfObj.resolve(); }, true, function () { dfObj.reject(); });
return dfObj;
}
});
// link event handlers
$('#checkupdate').click(function () { backend('check'); });
$('#upgrade').click(function () { upgrade_ui(false); });
@@ -740,6 +754,7 @@
$("#firmware_flavour").change();
if (fwconf['flavour'] !== '' || fwconf['reboot'] === '1' || fwconf['aux'] === '1') {
$("i.fa-toggle-off#show_advanced_firmware").click();
$("#show_advanced_frm_FirmwareSettings").click();
}
$.each(fwopts.families, function(key, value) {
@@ -1087,6 +1102,9 @@
</tbody>
</table>
</form>
<div class="content-box __mt">
{{ partial("layout_partials/base_form",['fields':settingsForm,'id':'frm_FirmwareSettings'])}}
</div>
</div>
</div>
</div>