mirror of
https://github.com/opnsense/core.git
synced 2026-05-28 04:34:51 -04:00
MVC: add support for pluggable dynamic menu items and move some existing parts out of the MenuSystem class (#10113)
(cherry picked from commit8b13deac0e) (cherry picked from commitdfdadb6274) (cherry picked from commit4a94e60a42) (cherry picked from commit5452c5bc90) (cherry picked from commit1216d603f9) (cherry picked from commit7cfb03be5d)
This commit is contained in:
parent
141f5a63fc
commit
2185cc02a2
10 changed files with 455 additions and 223 deletions
4
plist
4
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
|
||||
|
|
@ -766,6 +767,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
|
||||
|
|
@ -826,6 +828,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
|
||||
|
|
@ -2209,6 +2212,7 @@
|
|||
/usr/local/opnsense/www/js/opnsense.js
|
||||
/usr/local/opnsense/www/js/opnsense_bootgrid.js
|
||||
/usr/local/opnsense/www/js/opnsense_health.js
|
||||
/usr/local/opnsense/www/js/opnsense_menusystem.js
|
||||
/usr/local/opnsense/www/js/opnsense_status.js
|
||||
/usr/local/opnsense/www/js/opnsense_theme.js
|
||||
/usr/local/opnsense/www/js/opnsense_ui.js
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ class ControllerBase extends ControllerRoot
|
|||
'/ui/js/opnsense_theme.js',
|
||||
'/ui/js/opnsense_ui.js',
|
||||
'/ui/js/opnsense_status.js',
|
||||
// OPNsense Menusystem access
|
||||
'/ui/js/opnsense_menusystem.js',
|
||||
// bootstrap script
|
||||
'/ui/js/bootstrap.min.js',
|
||||
'/ui/js/bootstrap-select.min.js',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ class MenuItem
|
|||
* this items id (xml tag name)
|
||||
* @var item|string
|
||||
*/
|
||||
private $id = "";
|
||||
private $id = '';
|
||||
|
||||
/**
|
||||
* visible name, default same as id
|
||||
|
|
@ -62,13 +62,19 @@ class MenuItem
|
|||
* layout information, icon
|
||||
* @var string
|
||||
*/
|
||||
private $CssClass = "";
|
||||
private $CssClass = '';
|
||||
|
||||
/**
|
||||
* Classes to add to the link
|
||||
* @var string
|
||||
*/
|
||||
private $LinkClass = '';
|
||||
|
||||
/**
|
||||
* link to url location
|
||||
* @var string
|
||||
*/
|
||||
private $Url = "";
|
||||
private $Url = '';
|
||||
|
||||
/**
|
||||
* link to external page
|
||||
|
|
@ -94,6 +100,12 @@ class MenuItem
|
|||
*/
|
||||
private $selected = false;
|
||||
|
||||
/**
|
||||
* Tree depth
|
||||
* @var int
|
||||
*/
|
||||
private $depth = 0;
|
||||
|
||||
/**
|
||||
* class method getters
|
||||
* @var array
|
||||
|
|
@ -141,6 +153,9 @@ class MenuItem
|
|||
$this->id = $id;
|
||||
$this->visibleName = gettext($id);
|
||||
$this->parent = $parent;
|
||||
if ($parent !== null) {
|
||||
$this->depth = $parent->getDepth() + 1;
|
||||
}
|
||||
$prop_exclude_list = ['getXmlPropertySetterName' => true];
|
||||
if (self::$internalClassMethodAliases === null) {
|
||||
self::$internalClassMethodAliases = [];
|
||||
|
|
@ -169,6 +184,14 @@ class MenuItem
|
|||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* return this nodes depth in the menu
|
||||
*/
|
||||
public function getDepth()
|
||||
{
|
||||
return $this->depth;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* set sort order
|
||||
|
|
@ -242,6 +265,40 @@ class MenuItem
|
|||
return $this->CssClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter for default link class
|
||||
* @param $value
|
||||
*/
|
||||
public function setLinkClass($value)
|
||||
{
|
||||
$this->LinkClass = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getter for css class set on the actual link
|
||||
*/
|
||||
public function getLinkClass()
|
||||
{
|
||||
$css = ['list-group-item'];
|
||||
if (count($this->children) >= 1 && $this->depth < 3) {
|
||||
if ($this->selected) {
|
||||
$css[] = 'active-menu-title';
|
||||
}
|
||||
} else {
|
||||
if ($this->depth == 3) {
|
||||
$css[] = 'menu-level-3-item';
|
||||
}
|
||||
if ($this->selected) {
|
||||
$css[] = 'active';
|
||||
}
|
||||
}
|
||||
if ($this->Url != '') {
|
||||
$css[] = 'menu_ref_' . md5($this->Url);
|
||||
}
|
||||
return implode(' ', $css) . " " . $this->LinkClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter for url field
|
||||
* @param $value
|
||||
|
|
@ -398,7 +455,7 @@ class MenuItem
|
|||
foreach ($this->children as $nodeId => &$node) {
|
||||
if ($node->isVisible()) {
|
||||
$node->toggleSelected($url);
|
||||
if ($node->getUrl() != "") {
|
||||
if ($node->getUrl() != '') {
|
||||
// hash part isn't available on server end
|
||||
$menuItemUrl = explode("#", $node->getUrl())[0];
|
||||
$match = str_replace([".", "*","?", "@"], ["\.", ".*","\?", "\@"], $menuItemUrl);
|
||||
|
|
|
|||
|
|
@ -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,189 +213,20 @@ class MenuSystem
|
|||
}
|
||||
}
|
||||
|
||||
$config = Config::getInstance()->object();
|
||||
|
||||
// collect interfaces for dynamic (interface) menu tabs...
|
||||
$iftargets = ['if' => [], 'gr' => [], 'wl' => [], 'fw' => [], 'dhcp4' => [], 'dhcp6' => []];
|
||||
$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);
|
||||
// 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();
|
||||
}
|
||||
|
||||
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))) {
|
||||
$iftargets['dhcp4'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
|
||||
}
|
||||
if (!empty(filter_var($node->ipaddrv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) || (string)$node->ipaddrv6 == 'track6' || (string)$node->ipaddrv6 == 'idassoc6') {
|
||||
$iftargets['dhcp6'][$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++,
|
||||
]);
|
||||
}
|
||||
|
||||
// add interfaces to "Firewall: Rules" menu tab...
|
||||
$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) {
|
||||
$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]",
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
$this->appendItem('Services.ISC_DHCPv4.' . $key, 'Edit' . $key, [
|
||||
'url' => '/services_dhcp.php?if=' . $key . '&*',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
$this->appendItem('Services.ISC_DHCPv4.' . $key, 'AddStatic' . $key, [
|
||||
'url' => '/services_dhcp_edit.php?if=' . $key,
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
$this->appendItem('Services.ISC_DHCPv4.' . $key, 'EditStatic' . $key, [
|
||||
'url' => '/services_dhcp_edit.php?if=' . $key . '&*',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
}
|
||||
$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]",
|
||||
'order' => $ordid++,
|
||||
]);
|
||||
$this->appendItem('Services.ISC_DHCPv6.' . $key, 'Add' . $key, [
|
||||
'url' => '/services_dhcpv6_edit.php?if=' . $key,
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
$this->appendItem('Services.ISC_DHCPv6.' . $key, 'Edit' . $key, [
|
||||
'url' => '/services_dhcpv6_edit.php?if=' . $key . '&*',
|
||||
'visibility' => 'hidden',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
84
src/opnsense/mvc/app/models/OPNsense/Firewall/Menu/Menu.php
Normal file
84
src/opnsense/mvc/app/models/OPNsense/Firewall/Menu/Menu.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?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...
|
||||
$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) {
|
||||
$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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/opnsense/mvc/app/models/OPNsense/Interfaces/Menu/Menu.php
Normal file
128
src/opnsense/mvc/app/models/OPNsense/Interfaces/Menu/Menu.php
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<?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++,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,14 +5,14 @@
|
|||
<div class="panel list-group" style="border:0px">
|
||||
{% for topMenuItem in menuSystem %}
|
||||
{% if topMenuItem.Children|length >= 1 %}
|
||||
<a href="#{{ topMenuItem.Id }}" class="list-group-item {% if topMenuItem.Selected %} active-menu-title {% endif %}" data-toggle="collapse" data-parent="#mainmenu">
|
||||
<a href="#{{ topMenuItem.Id }}" class="{{ topMenuItem.LinkClass }}" data-toggle="collapse" data-parent="#mainmenu">
|
||||
<span class="{{ topMenuItem.CssClass }} __iconspacer"></span><span style="word-break: keep-all">{{ topMenuItem.VisibleName }}</span>
|
||||
</a>
|
||||
<div class="collapse {% if topMenuItem.Selected %} active-menu in {% endif %}" id="{{ topMenuItem.Id }}">
|
||||
{% for subMenuItem in topMenuItem.Children %}
|
||||
{% if subMenuItem.Url == '' %}
|
||||
{# next level items, submenu is a container #}
|
||||
<a href="#{{ topMenuItem.Id }}_{{ subMenuItem.Id }}" class="list-group-item {% if subMenuItem.Selected %} active-menu-title {% endif %}"
|
||||
<a href="#{{ topMenuItem.Id }}_{{ subMenuItem.Id }}" class="{{ subMenuItem.LinkClass }}"
|
||||
data-toggle="collapse" data-parent="#{{ topMenuItem.Id }}">
|
||||
<div style="display: table;width: 100%;">
|
||||
<div style="display: table-row">
|
||||
|
|
@ -25,13 +25,13 @@
|
|||
</a>
|
||||
<div class="collapse {% if subMenuItem.Selected %} active-menu in {% endif %}" id="{{ topMenuItem.Id }}_{{ subMenuItem.Id }}">
|
||||
{% for subsubMenuItem in subMenuItem.Children %} {% if subsubMenuItem.IsExternal == "Y" %}
|
||||
<a href="{{ subsubMenuItem.Url }}" target="_blank" rel="noopener noreferrer" class="list-group-item menu-level-3-item {% if subsubMenuItem.Selected %} active {% endif %}">{{ subsubMenuItem.VisibleName }}</a>
|
||||
<a href="{{ subsubMenuItem.Url }}" target="_blank" rel="noopener noreferrer" class="{{ subsubMenuItem.LinkClass }}">{{ subsubMenuItem.VisibleName }}</a>
|
||||
{% elseif acl.isPageAccessible(session.get('Username'),subsubMenuItem.Url) %}
|
||||
<a href="{{ subsubMenuItem.Url }}" class="list-group-item menu-level-3-item {% if subsubMenuItem.Selected %} active {% endif %}">{{ subsubMenuItem.VisibleName }}</a>
|
||||
<a href="{{ subsubMenuItem.Url }}" class="{{ subsubMenuItem.LinkClass }}">{{ subsubMenuItem.VisibleName }}</a>
|
||||
{% endif %} {% endfor %}
|
||||
</div>
|
||||
{% elseif subMenuItem.IsExternal == "Y" %}
|
||||
<a href="{{ subMenuItem.Url }}" target="_blank" rel="noopener noreferrer" class="list-group-item {% if subMenuItem.Selected %} active {% endif %}"
|
||||
<a href="{{ subMenuItem.Url }}" target="_blank" rel="noopener noreferrer" class="{{ subMenuItem.LinkClass }}"
|
||||
aria-expanded="{% if subMenuItem.Selected %}true{%else%}false{% endif %}">
|
||||
<div style="display: table;width: 100%;">
|
||||
<div style="display: table-row">
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
</a>
|
||||
{% elseif acl.isPageAccessible(session.get('Username'),subMenuItem.Url) %}
|
||||
<a href="{{ subMenuItem.Url }}" class="list-group-item {% if subMenuItem.Selected %} active {% endif %}">
|
||||
<a href="{{ subMenuItem.Url }}" class="{{ subMenuItem.LinkClass }}">
|
||||
<div style="display: table;width: 100%;">
|
||||
<div style="display: table-row">
|
||||
<div style="display: table-cell">{{ subMenuItem.VisibleName }}</div>
|
||||
|
|
@ -59,11 +59,11 @@
|
|||
{% else %}
|
||||
{# parent level link menu items that pivot #}
|
||||
{% if topMenuItem.IsExternal == "Y" %}
|
||||
<a href="{{ topMenuItem.Url }}" target="_blank" rel="noopener noreferrer" class="list-group-item {% if topMenuItem.Selected %} active-menu-title {% endif %}" data-parent="#mainmenu">
|
||||
<a href="{{ topMenuItem.Url }}" target="_blank" rel="noopener noreferrer" class="{{ topMenuItem.LinkClass }}" data-parent="#mainmenu">
|
||||
<span class="{{ topMenuItem.CssClass }} __iconspacer"></span>{{ topMenuItem.VisibleName }}
|
||||
</a>
|
||||
{% elseif acl.isPageAccessible(session.get('Username'),topMenuItem.Url) %}
|
||||
<a href="{{ topMenuItem.Url }}" class="list-group-item {% if topMenuItem.Selected %} active-menu-title {% endif %}" data-parent="#mainmenu">
|
||||
<a href="{{ topMenuItem.Url }}" class="{{ topMenuItem.LinkClass }}" data-parent="#mainmenu">
|
||||
<span class="{{ topMenuItem.CssClass }} __iconspacer"></span>{{ topMenuItem.VisibleName }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
|||
63
src/opnsense/www/js/opnsense_menusystem.js
Normal file
63
src/opnsense/www/js/opnsense_menusystem.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class MenuItem {
|
||||
constructor(node) {
|
||||
this.href = node.attr("href");
|
||||
this._obj = node;
|
||||
this.breadcrumb();
|
||||
}
|
||||
|
||||
breadcrumb() {
|
||||
let result = [this._obj.text().trim()];
|
||||
let this_ref = this._obj;
|
||||
let parent_div = this_ref.closest('div');
|
||||
while (parent_div && parent_div.attr('id')) {
|
||||
let container = $("a[href='#" + parent_div.attr('id')+"']");
|
||||
result.push(container.text().trim());
|
||||
parent_div = parent_div.parent().closest('div');
|
||||
}
|
||||
result.reverse()
|
||||
return result.join(': ');
|
||||
}
|
||||
}
|
||||
|
||||
class MenuSystem {
|
||||
constructor() {
|
||||
this._menusystem = $("#mainmenu");
|
||||
};
|
||||
|
||||
* walk (){
|
||||
for (const node of this._menusystem.find("a.list-group-item").toArray()) {
|
||||
const element = $(node);
|
||||
const href = element.attr("href");
|
||||
if (href && !href.startsWith("#")) {
|
||||
/* only yield leaves */
|
||||
yield new MenuItem(element);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -89,13 +89,13 @@ $aclObj = new \OPNsense\Core\ACL();
|
|||
foreach($menuSystem as $topMenuItem): ?>
|
||||
<?php
|
||||
if (count($topMenuItem->Children) >= 1): ?>
|
||||
<a href="#<?=$topMenuItem->Id;?>" class="list-group-item <?= $topMenuItem->Selected ? 'active-menu-title' : ''; ?>" data-toggle="collapse" data-parent="#mainmenu"><span class="<?=$topMenuItem->CssClass;?> __iconspacer"></span><span style="word-break: keep-all"><?= html_safe($topMenuItem->VisibleName) ?></span></a>
|
||||
<a href="#<?=$topMenuItem->Id;?>" class="<?= $topMenuItem->LinkClass; ?>" data-toggle="collapse" data-parent="#mainmenu"><span class="<?=$topMenuItem->CssClass;?> __iconspacer"></span><span style="word-break: keep-all"><?= html_safe($topMenuItem->VisibleName) ?></span></a>
|
||||
<div class="collapse <?=$topMenuItem->Selected ? 'active-menu in' :'';?>" id="<?=$topMenuItem->Id;?>">
|
||||
<?php
|
||||
foreach($topMenuItem->Children as $subMenuItem): ?>
|
||||
<?php
|
||||
if ($subMenuItem->Url == '' ):?>
|
||||
<a href="#<?=$topMenuItem->Id;?>_<?=$subMenuItem->Id;?>" class="list-group-item <?=$subMenuItem->Selected ? "active-menu-title" : '';?>" data-toggle="collapse" data-parent="#<?=$topMenuItem->Id;?>" aria-expanded="<?=$subMenuItem->Selected ? "true" : "false";?>">
|
||||
<a href="#<?=$topMenuItem->Id;?>_<?=$subMenuItem->Id;?>" class="<?= $subMenuItem->LinkClass; ?>" data-toggle="collapse" data-parent="#<?=$topMenuItem->Id;?>" aria-expanded="<?=$subMenuItem->Selected ? "true" : "false";?>">
|
||||
<div style="display: table;width: 100%;">
|
||||
<div style="display: table-row">
|
||||
<div style="display: table-cell"><?= html_safe($subMenuItem->VisibleName) ?></div>
|
||||
|
|
@ -108,17 +108,17 @@ $aclObj = new \OPNsense\Core\ACL();
|
|||
foreach ($subMenuItem->Children as $subsubMenuItem):?>
|
||||
<?php
|
||||
if ($subsubMenuItem->IsExternal == "Y"):?>
|
||||
<a href="<?=$subsubMenuItem->Url;?>" target="_blank" rel="noopener noreferrer" class="list-group-item menu-level-3-item <?=$subsubMenuItem->Selected ? 'active' :'';?>"><?= $subsubMenuItem->VisibleName ?></a>
|
||||
<a href="<?=$subsubMenuItem->Url;?>" target="_blank" rel="noopener noreferrer" class="<?= $subsubMenuItem->LinkClass; ?>"><?= $subsubMenuItem->VisibleName ?></a>
|
||||
<?php
|
||||
elseif ($aclObj->isPageAccessible($_SESSION['Username'],$subsubMenuItem->Url)):?>
|
||||
<a href="<?=$subsubMenuItem->Url;?>" class="list-group-item menu-level-3-item <?=$subsubMenuItem->Selected ? 'active' :'';?>"><?= $subsubMenuItem->VisibleName ?></a>
|
||||
<a href="<?=$subsubMenuItem->Url;?>" class="<?= $subsubMenuItem->LinkClass; ?>"><?= $subsubMenuItem->VisibleName ?></a>
|
||||
<?php
|
||||
endif;
|
||||
endforeach;?>
|
||||
</div>
|
||||
<?php
|
||||
elseif ($subMenuItem->IsExternal == "Y" ):?>
|
||||
<a href="<?=$subMenuItem->Url;?>" target="_blank" rel="noopener noreferrer" class="list-group-item <?=$subMenuItem->Selected ? 'active' : '';?>" aria-expanded="<?=$subMenuItem->Selected ? 'true' : 'false';?>">
|
||||
<a href="<?=$subMenuItem->Url;?>" target="_blank" rel="noopener noreferrer" class="<?= $subMenuItem->LinkClass; ?>" aria-expanded="<?=$subMenuItem->Selected ? 'true' : 'false';?>">
|
||||
<div style="display: table;width: 100%;">
|
||||
<div style="display: table-row">
|
||||
<div style="display: table-cell"><?= html_safe($subMenuItem->VisibleName) ?></div>
|
||||
|
|
@ -128,7 +128,7 @@ $aclObj = new \OPNsense\Core\ACL();
|
|||
</a>
|
||||
<?php
|
||||
elseif ($aclObj->isPageAccessible($_SESSION['Username'],$subMenuItem->Url)):?>
|
||||
<a href="<?=$subMenuItem->Url;?>" class="list-group-item <?=$subMenuItem->Selected ? 'active' :'';?>">
|
||||
<a href="<?=$subMenuItem->Url;?>" class="<?= $subMenuItem->LinkClass; ?>">
|
||||
<div style="display: table;width: 100%;">
|
||||
<div style="display: table-row">
|
||||
<div style="display: table-cell"><?= html_safe($subMenuItem->VisibleName) ?></div>
|
||||
|
|
@ -146,12 +146,12 @@ $aclObj = new \OPNsense\Core\ACL();
|
|||
else: ?>
|
||||
<?php
|
||||
if ($topMenuItem->IsExternal == "Y" ):?>
|
||||
<a href="<?=$topMenuItem->Url;?>" target="_blank" rel="noopener noreferrer" class="list-group-item <?=$topMenuItem->Selected ? 'active-menu-title' : '';?>" data-parent="#mainmenu">
|
||||
<a href="<?=$topMenuItem->Url;?>" target="_blank" rel="noopener noreferrer" class="<?= $topMenuItem->LinkClass; ?>" data-parent="#mainmenu">
|
||||
<span class="<?=$topMenuItem->CssClass;?> __iconspacer"></span><?= html_safe($topMenuItem->VisibleName) ?>
|
||||
</a>
|
||||
<?php
|
||||
elseif ($aclObj->isPageAccessible($_SESSION['Username'],$topMenuItem->Url)):?>
|
||||
<a href="<?=$topMenuItem->Url;?>" class="list-group-item <?=$topMenuItem->Selected ? 'active-menu-title' : '';?>" data-parent="#mainmenu">
|
||||
<a href="<?=$topMenuItem->Url;?>" class="<?= $topMenuItem->LinkClass; ?>" data-parent="#mainmenu">
|
||||
<span class="<?=$topMenuItem->CssClass;?> __iconspacer"></span><?= html_safe($topMenuItem->VisibleName) ?>
|
||||
</a>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue