marko/admin-panel
Server-rendered admin panel UI --- provides login, dashboard, and permission-filtered sidebar navigation out of the box. Admin Panel delivers the server-rendered HTML interface for the admin area. It includes a login/logout flow, a dashboard that displays registered admin sections, and a menu builder that filters navigation items by the current user’s permissions. Templates are rendered via ViewInterface using the admin-panel:: namespace. All routes are protected by AdminAuthMiddleware.
Installation
Section titled “Installation”composer require marko/admin-panelRequires marko/admin, marko/admin-auth, marko/security, marko/ratelimiter, a cache driver (for login throttling), a view driver, and a template sibling package. Install marko/admin-panel-latte with marko/view-latte, or marko/admin-panel-twig with marko/view-twig.
Built-in Routes
Section titled “Built-in Routes”The panel registers these routes automatically:
| Method | Path | Description |
|---|---|---|
| GET | /admin/login | Login form |
| POST | /admin/login | Authenticate (requires valid CSRF token) |
| POST | /admin/logout | Logout (requires valid CSRF token) |
| GET | /admin | Dashboard (requires auth; lists only the sections the admin user can access) |
The login and logout POST routes are protected by the global CsrfMiddleware that marko/security registers. The login form receives a CSRF token via the csrfToken template variable; the template must include it as a hidden field named _token. Requests without a valid token are rejected before authentication logic runs.
Login is throttled in two layers. The admin guard is a SessionGuard, so login throttling locks out an email for a client after repeated failures. A locked-out attempt re-renders the login form with an error, status 429 and a Retry-After header. On top of that, #[RateLimit(maxAttempts: 10, decaySeconds: 60, name: 'admin-login')] with RateLimitMiddleware caps login POSTs per client IP across all emails. Both need a cache driver.
Login and logout run on the admin guard (admin-auth.guard), which authenticates against AdminUserProvider. A frontend user can’t sign in here, and an admin login is separate from any frontend session.
Building the Sidebar Menu
Section titled “Building the Sidebar Menu”AdminMenuBuilderInterface produces a navigation structure filtered by the user’s permissions:
use Marko\AdminPanel\Menu\AdminMenuBuilderInterface;use Marko\AdminAuth\Entity\AdminUserInterface;
readonly class LayoutHelper{ public function __construct( private AdminMenuBuilderInterface $adminMenuBuilder, ) {}
public function getSidebar( AdminUserInterface $user, string $currentPath, ): array { return $this->adminMenuBuilder->build($user, $currentPath); // Returns: [ // ['section' => ..., 'items' => [...], 'active' => true, 'activeItemId' => 'products'], // ['section' => ..., 'items' => [...], 'active' => false, 'activeItemId' => null], // ] }}Each entry includes:
section--- theAdminSectionInterfaceinstanceitems--- filtered and sortedMenuItemInterfacearrayactive--- whether this section contains the current pageactiveItemId--- the ID of the active menu item, or null
Dashboard Sections
Section titled “Dashboard Sections”Get sections the user can access for the dashboard overview:
$sections = $this->adminMenuBuilder->buildDashboardSections($user);// Returns AdminSectionInterface[] with at least one accessible menu itemTemplate Overrides
Section titled “Template Overrides”Admin panel templates live under the admin-panel:: namespace. Override them by placing templates in your module’s resources/views/ directory with the same path:
mymodule/ resources/ views/ admin-panel/ dashboard/ index.latte # Overrides the default dashboardConfiguration
Section titled “Configuration”config/admin-panel.php sets the browser title of the dashboard. DashboardController passes it to the template as pageTitle:
return [ 'page_title' => 'Marko Admin',];Customization
Section titled “Customization”Replace the menu builder via Preferences to customize navigation behavior:
use Marko\Core\Attributes\Preference;use Marko\AdminPanel\Menu\AdminMenuBuilder;use Marko\AdminAuth\Entity\AdminUserInterface;
#[Preference(replaces: AdminMenuBuilder::class)]class CustomMenuBuilder extends AdminMenuBuilder{ public function build( AdminUserInterface $user, string $currentPath = '', ): array { $menu = parent::build($user, $currentPath); // Add custom sections or reorder return $menu; }}API Reference
Section titled “API Reference”AdminMenuBuilderInterface
Section titled “AdminMenuBuilderInterface”interface AdminMenuBuilderInterface{ public function build(AdminUserInterface $user, string $currentPath = ''): array; public function buildDashboardSections(AdminUserInterface $user): array;}LoginController Routes
Section titled “LoginController Routes”use Marko\Routing\Attributes\Get;use Marko\Routing\Attributes\Post;
#[Get(path: '/admin/login')] // showLoginForm#[Post(path: '/admin/login')] // authenticate — CSRF required (global CsrfMiddleware)#[Middleware(RateLimitMiddleware::class)] // 10 login POSTs per minute per client IP#[RateLimit(maxAttempts: 10, decaySeconds: 60, name: 'admin-login')]#[Post(path: '/admin/logout')] // logout — CSRF required (global CsrfMiddleware)DashboardController Routes
Section titled “DashboardController Routes”use Marko\Routing\Attributes\Get;use Marko\Routing\Attributes\Middleware;use Marko\AdminAuth\Middleware\AdminAuthMiddleware;
#[Get(path: '/admin')] // index#[Middleware(AdminAuthMiddleware::class)] // requires authAdminPanelConfigInterface
Section titled “AdminPanelConfigInterface”interface AdminPanelConfigInterface{ public function getPageTitle(): string;}