Files
XC_VM/docs/en/development/navbar-rendering.md
T
Divarion_D c982bb2f1e docs: audit dev docs for source drift, split oversized pages, add core-wiring
Verify every dev-doc claim against src/ and fix factual drift: wrong method
signatures/return types, wrong enum casing (BootContext cases are PascalCase),
stale paths (M3u parsers are Composer deps under vendor/, MobileDetect is
mobiledetect/mobiledetectlib v4.9.0 \Detection\MobileDetect, NotFoundException
lives in XcVm\Core\Container\Psr), a fictional `stream:check` command/class,
reversed migration-failure semantics ([FAIL] = not recorded, retried),
inverted isStreamRunning/isStreamAlive descriptions, findProcessPIDs ANY-not-ALL,
acquireCronLock has no shutdown callback, and nonexistent make targets.

Split oversized pages and fix nav + cross-links:
- modules.md -> module-authoring / module-lifecycle / module-extension-points
- cli-tools.md -> cli-tools + database-migrations
- streaming-subsystem.md -> + streaming-diagnostics
- geoip-and-device-detection.md -> geoip-isp-and-geo-routing + device-detection-and-stb-locking

Add development/core-wiring.md: how the core assembles itself at boot
(container population, ServiceContainer reference, bootAll orchestration,
CLI command auto-discovery, end-to-end Admin/CLI boot walkthroughs).

Only docs/en + mkdocs.yml touched; docs/ru is regenerated before release.
2026-08-26 22:42:43 +03:00

125 lines
4.7 KiB
Markdown

# Navbar rendering in module panel
Technical documentation for building and rendering the navbar in the admin panel.
## Purpose
The navbar is built declaratively from a `NavbarItem` tree, not from hardcoded HTML menus.
Tree sources:
1. Core nodes from `CoreNavbarProvider::register()`.
2. Module nodes from `ModuleInterface::registerNavbar()`.
## Lifecycle
1. `ModuleLoader::bootAll()` calls `CoreNavbarProvider::register()`.
2. Then `registerNavbar()` is called for each loaded module.
3. In `Public/Views/admin/header.php`, the tree is rendered from `NavbarRegistry`.
## Rendering in header
Rendering is done by helper functions:
1. `_xc_nav_visible()` - visibility filtering for a node.
2. `_xc_nav_label()` - text label resolution.
3. `_xc_nav_children()` - recursive rendering of child items.
Top-level nodes come from `NavbarRegistry::getTopLevel()`, child nodes come from `NavbarRegistry::getChildren($key)`.
## Visibility rules
Checks are performed in `_xc_nav_visible()`:
1. `desktopOnly`: hides node on mobile.
2. `settingDisabled`: hides node when a setting flag is enabled.
3. `permissions`: OR-check via `Authorization::check('adv', $permission)`.
4. A group with `url='#'` is shown only if at least one child is visible.
5. `divider` is always passed through and rendered as separator.
## Rendering specifics
1. `divider` renders as separator without a link.
2. `submenuClass('megamenu')` enables two-column rendering for long lists.
3. `noMobileSubmenu` disables child submenu expansion on mobile.
## How a module adds a menu item
A module adds items only through `registerNavbar()`:
```php
public function registerNavbar(NavbarRegistry $registry): void {
NavbarRegistry::add((new NavbarItem('management.service_setup.my_module'))
->parent('management.service_setup')
->url('my_module')
->label('my_module')
->permissions(['my_module'])
->order(60));
NavbarRegistry::add((new NavbarItem('management.logs.my_module_log'))
->parent('management.logs')
->url('my_module_logs')
->label('', 'My Module Logs')
->permissions(['my_module'])
->order(170));
}
```
## NavbarItem builder API
`NavbarItem` is a fluent value object (`src/Core/Module/NavbarItem.php`) — chain setters off `new NavbarItem($key)`:
| Method | Purpose |
| --- | --- |
| `new NavbarItem($key)` | create a node; `$key` is its unique `section.group.item` id |
| `->parent($parentKey)` | attach under an existing node (omit for a top-level node) |
| `->url($url)` | target path; `'#'` makes it a non-navigating **group** header |
| `->label($key, $fallback = '')` | translation key, or `('', 'Literal')` for fixed text |
| `->icon($icon)` | icon CSS class for the item |
| `->permissions([...])` | OR-list of permission keys; node hidden unless the viewer has one |
| `->order($n)` | sort position within the parent |
| `->desktopOnly()` | hide on mobile |
| `->noMobileSubmenu()` | don't expand this node's submenu on mobile |
| `->submenuClass('megamenu')` | two-column rendering for long child lists |
| `->settingDisabled($settingKey)` | hide the node when that panel setting flag is truthy |
| `->makeDivider()` | render this node as a separator (no link) |
### Group node and divider
```php
public function registerNavbar(NavbarRegistry $registry): void {
// A group header (url('#')) — shown only if at least one child is visible
NavbarRegistry::add((new NavbarItem('management.my_group'))
->parent('management')
->url('#')
->label('my_group')
->order(50));
// A divider inside that group
NavbarRegistry::add((new NavbarItem('management.my_group.sep1'))
->parent('management.my_group')
->makeDivider()
->order(55));
}
```
> `settingDisabled('some_setting')` hides the node whenever that setting is truthy (gate a feature behind a toggle). Visibility is also **viewer-scoped**: the `permissions` OR-check runs against the current user via `Authorization::check('adv', …)`, so an admin and a reseller can see different subsets of the same tree.
## Practical rules for modules
1. Use unique `key` values in `section.group.item` format.
2. Set `parent` to an existing core tree node or your own already-added node.
3. Position items using `order` inside one parent.
4. Use `label('translation_key')` for translatable text.
5. Use `label('', 'Literal Text')` for fixed literal text.
6. If the module has no menu items, keep `registerNavbar()` empty.
## Related files
| File | Role |
| --- | --- |
| `src/Core/Module/NavbarRegistry.php` | Collects navbar items from providers |
| `src/Core/Module/NavbarItem.php` | Navbar item value object |
| `src/Core/Module/CoreNavbarProvider.php` | Built-in core menu items |
| `src/Public/Views/admin/header.php` | Renders the navbar tree |