mirror of
https://github.com/opnsense/core.git
synced 2026-06-09 00:42:36 -04:00
MVC: add support for pluggable dynamic menu items and move some existing parts out of the MenuSystem class
In most cases we use static menu registartions, but there are exceptions which depend on interfaces for example. While looking at https://github.com/opnsense/core/pull/10033, a longer standing wish came up again, which is the reason to add this support right now. It also helps in removing some legacy components for good via plugins. To register new menu items, the following pattern may be used: * In your model, derive a Menu class from MenuContainer * implement a method collect() which should add new menu items via the appendItem() {bound to appendItem in MenuSystem} Always try to minimize the amount of code inside these plugins as this code will be executed on each page load.
This commit is contained in:
parent
016f66cb46
commit
df346012ae
5 changed files with 338 additions and 163 deletions
3
plist
3
plist
|
|
@ -655,6 +655,7 @@
|
|||
/usr/local/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/UpdateOnlyTextField.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/UrlField.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/VirtualIPField.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Base/Menu/MenuContainer.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Base/Menu/MenuInitException.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Base/Menu/MenuItem.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Base/Menu/MenuSystem.php
|
||||
|
|
@ -767,6 +768,7 @@
|
|||
/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Filter.xml
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Group.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Group.xml
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Menu/Menu.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Menu/Menu.xml
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Migrations/M1_0_0.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Migrations/MFP1_0_0.php
|
||||
|
|
@ -827,6 +829,7 @@
|
|||
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Lagg.xml
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Loopback.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Loopback.xml
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Menu/Menu.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Menu/Menu.xml
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Migrations/SET1_0_0.php
|
||||
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Neighbor.php
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
<?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\Base\Menu;
|
||||
|
||||
abstract class MenuContainer
|
||||
{
|
||||
private ?MenuSystem $menusystem = null;
|
||||
|
||||
public function __construct(MenuSystem $menusystem)
|
||||
{
|
||||
$this->menusystem = $menusystem;
|
||||
}
|
||||
|
||||
public function appendItem($root, $id, $properties)
|
||||
{
|
||||
return $this->menusystem->appendItem($root, $id, $properties);
|
||||
}
|
||||
|
||||
public function collect()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
namespace OPNsense\Base\Menu;
|
||||
|
||||
use ReflectionClass;
|
||||
use OPNsense\Core\AppConfig;
|
||||
use OPNsense\Core\Config;
|
||||
|
||||
|
|
@ -48,6 +49,11 @@ class MenuSystem
|
|||
*/
|
||||
private $menuCacheFilename = null;
|
||||
|
||||
/**
|
||||
* @var array model directories
|
||||
*/
|
||||
private $modelDirs = [];
|
||||
|
||||
/**
|
||||
* @var int time to live for merged menu xml
|
||||
*/
|
||||
|
|
@ -106,6 +112,20 @@ class MenuSystem
|
|||
@unlink($this->menuCacheFilename);
|
||||
}
|
||||
|
||||
private function iterateMenuPaths()
|
||||
{
|
||||
foreach ($this->modelDirs as $modelDir) {
|
||||
foreach (glob(preg_replace('#/+#', '/', "{$modelDir}/*")) as $vendor) {
|
||||
foreach (glob($vendor . '/*') as $module) {
|
||||
if (is_dir($module . '/Menu')) {
|
||||
$path = $module . '/Menu/';
|
||||
yield ['path' => $path, 'base' => substr($path, strlen($modelDir))];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and persist Menu configuration to disk.
|
||||
* @param bool $nowait when the cache is locked, skip waiting for it to become available.
|
||||
|
|
@ -113,33 +133,19 @@ class MenuSystem
|
|||
*/
|
||||
public function persist($nowait = true)
|
||||
{
|
||||
// fetch our model locations
|
||||
$appconfig = new AppConfig();
|
||||
if (!empty($appconfig->application->modelsDir)) {
|
||||
$modelDirs = $appconfig->application->modelsDir;
|
||||
if (!is_array($modelDirs) && !is_object($modelDirs)) {
|
||||
$modelDirs = array($modelDirs);
|
||||
}
|
||||
}
|
||||
|
||||
// collect all XML menu definitions into a single file
|
||||
$menuXml = new \DOMDocument('1.0');
|
||||
$root = $menuXml->createElement('menu');
|
||||
$menuXml->appendChild($root);
|
||||
// crawl all vendors and modules and add menu definitions
|
||||
foreach ($modelDirs as $modelDir) {
|
||||
foreach (glob(preg_replace('#/+#', '/', "{$modelDir}/*")) as $vendor) {
|
||||
foreach (glob($vendor . '/*') as $module) {
|
||||
$menu_cfg_xml = $module . '/Menu/Menu.xml';
|
||||
if (file_exists($menu_cfg_xml)) {
|
||||
try {
|
||||
$domNode = dom_import_simplexml($this->addXML($menu_cfg_xml));
|
||||
$domNode = $root->ownerDocument->importNode($domNode, true);
|
||||
$root->appendChild($domNode);
|
||||
} catch (MenuInitException $e) {
|
||||
error_log($e);
|
||||
}
|
||||
}
|
||||
foreach ($this->iterateMenuPaths() as $menu_dir) {
|
||||
if (file_exists($menu_dir['path'] . 'Menu.xml')) {
|
||||
try {
|
||||
$domNode = dom_import_simplexml($this->addXML($menu_dir['path'] . 'Menu.xml'));
|
||||
$domNode = $root->ownerDocument->importNode($domNode, true);
|
||||
$root->appendChild($domNode);
|
||||
} catch (MenuInitException $e) {
|
||||
error_log($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -179,8 +185,16 @@ class MenuSystem
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$appconfig = new AppConfig();
|
||||
if (!empty($appconfig->application->modelsDir)) {
|
||||
$this->modelDirs = $appconfig->application->modelsDir;
|
||||
if (!is_array($this->modelDirs) && !is_object($this->modelDirs)) {
|
||||
$this->modelDirs = [$this->modelDirs];
|
||||
}
|
||||
}
|
||||
|
||||
// set cache location
|
||||
$this->menuCacheFilename = (new AppConfig())->application->tempDir . '/opnsense_menu_cache.xml';
|
||||
$this->menuCacheFilename = $appconfig->application->tempDir . '/opnsense_menu_cache.xml';
|
||||
|
||||
// load menu xml's
|
||||
$menuxml = null;
|
||||
|
|
@ -199,47 +213,33 @@ class MenuSystem
|
|||
}
|
||||
}
|
||||
|
||||
// collect and insert dynamic entries
|
||||
foreach ($this->iterateMenuPaths() as $menu_dir) {
|
||||
if (file_exists($menu_dir['path'] . 'Menu.php')) {
|
||||
$classname = str_replace('/', '\\', $menu_dir['base']) . 'Menu';
|
||||
try {
|
||||
$cls = new ReflectionClass($classname);
|
||||
if (!$cls->isInstantiable() || !$cls->isSubclassOf('OPNsense\\Base\\Menu\\MenuContainer')) {
|
||||
continue; /* ignore, not ours */
|
||||
}
|
||||
} catch (\ReflectionException) {
|
||||
continue; /* ignore, can't construct */
|
||||
}
|
||||
$cls->newInstance($this)->collect();
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX: move to ISC plugin */
|
||||
if (!file_exists('/usr/local/www/services_dhcp.php') && !file_exists('/usr/local/www/services_dhcpv6.php')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = Config::getInstance()->object();
|
||||
|
||||
// collect interfaces for dynamic (interface) menu tabs...
|
||||
$iftargets = ['if' => [], 'gr' => [], 'wl' => [], 'fw' => [], 'dhcp4' => [], 'dhcp6' => []];
|
||||
$ifgroups = [];
|
||||
$ifgroups_seq = [];
|
||||
|
||||
$iftargets = ['dhcp4' => [], 'dhcp6' => []];
|
||||
if ($config->interfaces->count() > 0) {
|
||||
if ($config->ifgroups->count() > 0) {
|
||||
foreach ($config->ifgroups->children() as $key => $node) {
|
||||
if (empty($node->members) || !empty($node->nogroup)) {
|
||||
continue;
|
||||
}
|
||||
if (!empty((string)$node->sequence)) {
|
||||
$ifgroups_seq[(string)$node->ifname] = (int)((string)$node->sequence);
|
||||
}
|
||||
/* we need both if and gr reference */
|
||||
$iftargets['if'][(string)$node->ifname] = (string)$node->ifname;
|
||||
$iftargets['gr'][(string)$node->ifname] = (string)$node->ifname;
|
||||
foreach (preg_split('/[ |,]+/', (string)$node->members) as $member) {
|
||||
if (!array_key_exists($member, $ifgroups)) {
|
||||
$ifgroups[$member] = [];
|
||||
}
|
||||
array_push($ifgroups[$member], (string)$node->ifname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($config->interfaces->children() as $key => $node) {
|
||||
// Interfaces tab
|
||||
if (empty($node->virtual)) {
|
||||
$iftargets['if'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
|
||||
}
|
||||
// Wireless status tab
|
||||
if (isset($node->wireless)) {
|
||||
$iftargets['wl'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
|
||||
}
|
||||
// "Firewall: Rules" menu tab...
|
||||
if (isset($node->enable) && $node->if != 'lo0') {
|
||||
$iftargets['fw'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
|
||||
}
|
||||
// "Services: DHCPv[46]" menu tab:
|
||||
if (empty($node->virtual) && isset($node->enable)) {
|
||||
if (!empty(filter_var($node->ipaddr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))) {
|
||||
|
|
@ -256,110 +256,9 @@ class MenuSystem
|
|||
natcasesort($iftargets[$tab]);
|
||||
}
|
||||
|
||||
// add groups and interfaces to "Interfaces" menu tab...
|
||||
$ordid = count($ifgroups_seq) > 0 ? max($ifgroups_seq) : 0;
|
||||
foreach ($iftargets['if'] as $key => $descr) {
|
||||
if (array_key_exists($key, $iftargets['gr'])) {
|
||||
$this->appendItem('Interfaces', $key, [
|
||||
'fixedname' => '[' . $descr . ']',
|
||||
'cssclass' => 'fa fa-sitemap',
|
||||
'order' => isset($ifgroups_seq[$key]) ? $ifgroups_seq[$key] : $ordid++,
|
||||
]);
|
||||
} elseif (!array_key_exists($key, $ifgroups)) {
|
||||
$this->appendItem('Interfaces', $key, [
|
||||
'url' => '/interfaces.php?if=' . $key,
|
||||
'fixedname' => '[' . $descr . ']',
|
||||
'cssclass' => 'fa fa-sitemap',
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ifgroups as $key => $groupings) {
|
||||
$first = true;
|
||||
foreach ($groupings as $grouping) {
|
||||
if (empty($iftargets['if'][$key])) {
|
||||
// referential integrity between ifgroups and interfaces isn't assured, skip when interface doesn't exist
|
||||
continue;
|
||||
}
|
||||
$this->appendItem('Interfaces.' . $grouping, $key, [
|
||||
'url' => '/interfaces.php?if=' . $key . '&group=' . $grouping,
|
||||
'fixedname' => '[' . $iftargets['if'][$key] . ']',
|
||||
'order' => array_search($key, array_keys($iftargets['if']))
|
||||
]);
|
||||
if ($first) {
|
||||
$this->appendItem('Interfaces.' . $grouping . '.' . $key, 'Origin', [
|
||||
'url' => '/interfaces.php?if=' . $key,
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
$first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ordid = 100;
|
||||
foreach ($iftargets['wl'] as $key => $descr) {
|
||||
$this->appendItem('Interfaces.Wireless', $key, [
|
||||
'fixedname' => sprintf(gettext('%s Status'), $descr),
|
||||
'url' => '/status_wireless.php?if=' . $key,
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
}
|
||||
|
||||
// add interfaces to "Firewall: Rules" menu tab...
|
||||
$has_legacy_fw = !empty($config->filter?->rule?->count());
|
||||
$has_mvc_fw = !empty($config->OPNsense?->Firewall?->Filter?->rules?->count());
|
||||
if ($has_legacy_fw) {
|
||||
$this->appendItem('Firewall.Rules', 'Migration', [
|
||||
'url' => '/ui/firewall/migration',
|
||||
'fixedname' => sprintf("<i class='fa fa-fw fa-gears'> </i> %s", gettext('Migration assistant')),
|
||||
'order' => 0,
|
||||
]);
|
||||
$iftargets['fw'] = array_merge(['FloatingRules' => gettext('Floating')], $iftargets['fw']);
|
||||
}
|
||||
$ordid = 1;
|
||||
foreach ($iftargets['fw'] as $key => $descr) {
|
||||
if ($has_mvc_fw && !$has_legacy_fw) {
|
||||
/* only search */
|
||||
$this->appendItem('Firewall.Rule', $key, [
|
||||
'url' => '/ui/firewall/filter/#interface=' . $key,
|
||||
'fixedname' => $descr,
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
/* legacy rules */
|
||||
$this->appendItem('Firewall.Rules', $key, [
|
||||
'url' => '/firewall_rules.php?if=' . $key,
|
||||
'fixedname' => $descr,
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
$this->appendItem('Firewall.Rules.' . $key, 'Select' . $key, [
|
||||
'url' => '/firewall_rules.php?if=' . $key . '&*',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
if ($key == 'FloatingRules') {
|
||||
$this->appendItem('Firewall.Rules.' . $key, 'Top' . $key, [
|
||||
'url' => '/firewall_rules.php',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
}
|
||||
$this->appendItem('Firewall.Rules.' . $key, 'Add' . $key, [
|
||||
'url' => '/firewall_rules_edit.php?if=' . $key,
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
$this->appendItem('Firewall.Rules.' . $key, 'Edit' . $key, [
|
||||
'url' => '/firewall_rules_edit.php?if=' . $key . '&*',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
}
|
||||
|
||||
// add interfaces to "Services: DHCPv[46]" menu tab:
|
||||
$ordid = 0;
|
||||
foreach ($iftargets['dhcp4'] as $key => $descr) {
|
||||
if (!file_exists('/usr/local/www/services_dhcp.php')) {
|
||||
break;
|
||||
}
|
||||
$this->appendItem('Services.ISC_DHCPv4', $key, [
|
||||
'url' => '/services_dhcp.php?if=' . $key,
|
||||
'fixedname' => "[$descr]",
|
||||
|
|
@ -380,9 +279,6 @@ class MenuSystem
|
|||
}
|
||||
$ordid = 0;
|
||||
foreach ($iftargets['dhcp6'] as $key => $descr) {
|
||||
if (!file_exists('/usr/local/www/services_dhcpv6.php')) {
|
||||
break;
|
||||
}
|
||||
$this->appendItem('Services.ISC_DHCPv6', $key, [
|
||||
'url' => '/services_dhcpv6.php?if=' . $key,
|
||||
'fixedname' => "[$descr]",
|
||||
|
|
|
|||
98
src/opnsense/mvc/app/models/OPNsense/Firewall/Menu/Menu.php
Normal file
98
src/opnsense/mvc/app/models/OPNsense/Firewall/Menu/Menu.php
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?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\Firewall\Menu;
|
||||
|
||||
use OPNsense\Base\Menu\MenuContainer;
|
||||
use OPNsense\Core\Config;
|
||||
|
||||
class Menu extends MenuContainer
|
||||
{
|
||||
public function collect()
|
||||
{
|
||||
$config = Config::getInstance()->object();
|
||||
$iftargets = [];
|
||||
if ($config->interfaces->count() > 0) {
|
||||
foreach ($config->interfaces->children() as $key => $node) {
|
||||
// "Firewall: Rules" menu tab...
|
||||
if (isset($node->enable) && $node->if != 'lo0') {
|
||||
$iftargets[$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
natcasesort($iftargets);
|
||||
|
||||
// add interfaces to "Firewall: Rules" menu tab...
|
||||
$has_legacy_fw = !empty($config->filter?->rule?->count());
|
||||
$has_mvc_fw = !empty($config->OPNsense?->Firewall?->Filter?->rules?->count());
|
||||
if ($has_legacy_fw) {
|
||||
$this->appendItem('Firewall.Rules', 'Migration', [
|
||||
'url' => '/ui/firewall/migration',
|
||||
'fixedname' => sprintf("<i class='fa fa-fw fa-gears'> </i> %s", gettext('Migration assistant')),
|
||||
'order' => 0,
|
||||
]);
|
||||
$iftargets = array_merge(['FloatingRules' => gettext('Floating')], $iftargets);
|
||||
}
|
||||
$ordid = 1;
|
||||
foreach ($iftargets as $key => $descr) {
|
||||
if ($has_mvc_fw && !$has_legacy_fw) {
|
||||
/* only search */
|
||||
$this->appendItem('Firewall.Rule', $key, [
|
||||
'url' => '/ui/firewall/filter/#interface=' . $key,
|
||||
'fixedname' => $descr,
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
/* legacy rules */
|
||||
$this->appendItem('Firewall.Rules', $key, [
|
||||
'url' => '/firewall_rules.php?if=' . $key,
|
||||
'fixedname' => $descr,
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
$this->appendItem('Firewall.Rules.' . $key, 'Select' . $key, [
|
||||
'url' => '/firewall_rules.php?if=' . $key . '&*',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
if ($key == 'FloatingRules') {
|
||||
$this->appendItem('Firewall.Rules.' . $key, 'Top' . $key, [
|
||||
'url' => '/firewall_rules.php',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
}
|
||||
$this->appendItem('Firewall.Rules.' . $key, 'Add' . $key, [
|
||||
'url' => '/firewall_rules_edit.php?if=' . $key,
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
$this->appendItem('Firewall.Rules.' . $key, 'Edit' . $key, [
|
||||
'url' => '/firewall_rules_edit.php?if=' . $key . '&*',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
src/opnsense/mvc/app/models/OPNsense/Interfaces/Menu/Menu.php
Normal file
129
src/opnsense/mvc/app/models/OPNsense/Interfaces/Menu/Menu.php
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<?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\Interfaces\Menu;
|
||||
|
||||
use OPNsense\Base\Menu\MenuContainer;
|
||||
use OPNsense\Core\Config;
|
||||
|
||||
class Menu extends MenuContainer
|
||||
{
|
||||
public function collect()
|
||||
{
|
||||
$config = Config::getInstance()->object();
|
||||
$iftargets = ['if' => [], 'gr' => [], 'wl' => []];
|
||||
$ifgroups = [];
|
||||
$ifgroups_seq = [];
|
||||
|
||||
if ($config->interfaces->count() > 0) {
|
||||
if ($config->ifgroups->count() > 0) {
|
||||
foreach ($config->ifgroups->children() as $key => $node) {
|
||||
if (empty($node->members) || !empty($node->nogroup)) {
|
||||
continue;
|
||||
}
|
||||
if (!empty((string)$node->sequence)) {
|
||||
$ifgroups_seq[(string)$node->ifname] = (int)((string)$node->sequence);
|
||||
}
|
||||
/* we need both if and gr reference */
|
||||
$iftargets['if'][(string)$node->ifname] = (string)$node->ifname;
|
||||
$iftargets['gr'][(string)$node->ifname] = (string)$node->ifname;
|
||||
foreach (preg_split('/[ |,]+/', (string)$node->members) as $member) {
|
||||
if (!array_key_exists($member, $ifgroups)) {
|
||||
$ifgroups[$member] = [];
|
||||
}
|
||||
array_push($ifgroups[$member], (string)$node->ifname);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($config->interfaces->children() as $key => $node) {
|
||||
// Interfaces tab
|
||||
if (empty($node->virtual)) {
|
||||
$iftargets['if'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
|
||||
}
|
||||
// Wireless status tab
|
||||
if (isset($node->wireless)) {
|
||||
$iftargets['wl'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (array_keys($iftargets) as $tab) {
|
||||
natcasesort($iftargets[$tab]);
|
||||
}
|
||||
|
||||
// add groups and interfaces to "Interfaces" menu tab...
|
||||
$ordid = count($ifgroups_seq) > 0 ? max($ifgroups_seq) : 0;
|
||||
foreach ($iftargets['if'] as $key => $descr) {
|
||||
if (array_key_exists($key, $iftargets['gr'])) {
|
||||
$this->appendItem('Interfaces', $key, [
|
||||
'fixedname' => '[' . $descr . ']',
|
||||
'cssclass' => 'fa fa-sitemap',
|
||||
'order' => isset($ifgroups_seq[$key]) ? $ifgroups_seq[$key] : $ordid++,
|
||||
]);
|
||||
} elseif (!array_key_exists($key, $ifgroups)) {
|
||||
$this->appendItem('Interfaces', $key, [
|
||||
'url' => '/interfaces.php?if=' . $key,
|
||||
'fixedname' => '[' . $descr . ']',
|
||||
'cssclass' => 'fa fa-sitemap',
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ifgroups as $key => $groupings) {
|
||||
$first = true;
|
||||
foreach ($groupings as $grouping) {
|
||||
if (empty($iftargets['if'][$key])) {
|
||||
// referential integrity between ifgroups and interfaces isn't assured, skip when interface doesn't exist
|
||||
continue;
|
||||
}
|
||||
$this->appendItem('Interfaces.' . $grouping, $key, [
|
||||
'url' => '/interfaces.php?if=' . $key . '&group=' . $grouping,
|
||||
'fixedname' => '[' . $iftargets['if'][$key] . ']',
|
||||
'order' => array_search($key, array_keys($iftargets['if']))
|
||||
]);
|
||||
if ($first) {
|
||||
$this->appendItem('Interfaces.' . $grouping . '.' . $key, 'Origin', [
|
||||
'url' => '/interfaces.php?if=' . $key,
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
$first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ordid = 100;
|
||||
foreach ($iftargets['wl'] as $key => $descr) {
|
||||
$this->appendItem('Interfaces.Wireless', $key, [
|
||||
'fixedname' => sprintf(gettext('%s Status'), $descr),
|
||||
'url' => '/status_wireless.php?if=' . $key,
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue