Skip to content

MVC: add support for pluggable dynamic menu items and move some existing parts out of the MenuSystem class#10113

Merged
AdSchellevis merged 2 commits into
masterfrom
mvc_menu_pluggable
Apr 21, 2026
Merged

MVC: add support for pluggable dynamic menu items and move some existing parts out of the MenuSystem class#10113
AdSchellevis merged 2 commits into
masterfrom
mvc_menu_pluggable

Conversation

@AdSchellevis
Copy link
Copy Markdown
Member

In most cases we use static menu registrations, but there are exceptions which depend on interfaces for example. While looking at #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.

To explain the concept, both interface and firewall menu items have been moved to their new classes in this PR.

@AdSchellevis AdSchellevis self-assigned this Apr 9, 2026
@AdSchellevis AdSchellevis added the feature Adding new functionality label Apr 9, 2026

abstract class MenuContainer
{
private ?MenuSystem $menusystem = null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this is private, how will the favorites code access getItem() to build the breadcrumb labels for the Favorites menu?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Greelan see appendItem() examples in Firewall and Interfaces

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've looked at those. I think the favorites scenario is different, because the labels can't just be read from the config, instead they need to be built from the existing menu tree (eg a favorite pointing to /ui/firewall/filter should show as Firewall: Rules [new]). Perhaps I'm missing something?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might need some additional logic to prevent the favorite item needing the same uri as the actual target, but we'll see it when we'll get there. One of the options might be to just store a (md5) hash of the link and reflect the click action for example.

The favorite description itself could be part of the stored content to prevent the need to render the favorites if everything else is already there (since other dynamic content may have been set to favorite as well).

Copy link
Copy Markdown
Contributor

@Greelan Greelan Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree it will need further thinking down the track. I wanted to avoid storing the label in case it became stale (menu item breadcrumbs changed).

Saying that though made me realise that there is a similar issue - though less likely - for the endpoint itself, eg when a MVC migration occurs. It's a bit different since the pruning logic will clean it up, but the consequence is that the menu item will be un-favourited. Not fatal but not ideal from a UX perspective.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Greelan this f1ba1bd is likely the missing part for the favorites. The commit message contains some context, with this you can easily reference menu items and should be able to capture clicks on the favorite menu items (and pass them along)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I will have a closer look at it when I get a chance.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I've now spent some time with this and have done some re-work to use the pluggable system, link classes, and JS wrapper. Will need to wait until the next OPNsense release for testing as it is too challenging to patch an existing installation with the needed upstream commits, given they build on other commits as well

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI I don't think this will land in 26.1.7 next week but 26.1.8 is very likely.

…ing 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 #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.
…to be used for favorites.

This commit offers a couple of things, first of all it adds a unique class name on every traversable menu item so we can easily use javascript to pass a click event, for example:

$(".menu_ref_7e46272fe380827861cbaf5b484c43c9")[0].click()

We need this in order to link an item, but not use its actual uri in our href later, as this would confuse the "active" item selected.

Next, we offer the option to inject an additional link class ( e.g. $this->appendItem(..,..,['linkclass' => 'my_link_class']); ), as this offers a way to register on the "favorite" buttons.

Finally we cleanup the menu code a bit so the volt and legacy template contain less logic.
Copy link
Copy Markdown
Member

@fichtner fichtner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does LinkClass also work for the migration assistant icon?

@AdSchellevis
Copy link
Copy Markdown
Member Author

Does LinkClass also work for the migration assistant icon?

Nope, it's a class on the link, not on its content. I was doubting refactoring the whole menu, but since I wanted to limit the impact, only pushed the concepts in there that are currently missing. Eventually it would be practical to render this from Javascript and offer an endpoint which contains all relevant items, but that's quite some work I'm afraid.

@AdSchellevis AdSchellevis merged commit 8b13dea into master Apr 21, 2026
@AdSchellevis AdSchellevis deleted the mvc_menu_pluggable branch April 22, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Adding new functionality

Development

Successfully merging this pull request may close these issues.

3 participants