codeartlv / joona
A simple backend template for Laravel projects
Requires
- php: ^8.2
- jenssegers/agent: ^2.6
- tightenco/ziggy: ^2.0
- dev-main
- 1.30.6
- 1.30.5
- 1.30.4
- 1.30.3
- 1.30.2
- 1.30.1
- 1.30.0
- 1.29.2
- 1.29.1
- 1.29.0
- 1.28.8
- 1.28.7
- 1.28.6
- 1.28.5
- 1.28.4
- 1.28.3
- 1.28.2
- 1.28.1
- 1.28.0
- 1.27.2
- 1.27.1
- 1.27.0
- 1.26.22
- 1.26.21
- 1.26.20
- 1.26.19
- 1.26.18
- 1.26.17
- 1.26.16
- 1.26.15
- 1.26.14
- 1.26.13
- 1.26.12
- 1.26.11
- 1.26.10
- 1.26.9
- 1.26.8
- 1.26.7
- 1.26.6
- 1.26.5
- 1.26.4
- 1.26.3
- 1.26.2
- 1.26.1
- 1.26.0
- 1.25.2
- 1.25.1
- 1.25.0
- 1.24.6
- 1.24.5
- 1.24.4
- 1.24.3
- 1.24.2
- 1.24.1
- 1.24.0
- 1.23.8
- 1.23.7
- 1.23.6
- 1.23.5
- 1.23.4
- 1.23.3
- 1.23.2
- 1.23.1
- 1.22.15
- 1.22.14
- 1.22.13
- 1.22.12
- 1.22.11
- 1.22.10
- 1.22.9
- 1.22.8
- 1.22.7
- 1.22.6
- 1.22.5
- 1.22.4
- 1.22.3
- 1.22.2
- 1.22.1
- 1.22.0
- 1.21.10
- 1.21.9
- 1.21.8
- 1.21.7
- 1.21.6
- 1.21.5
- 1.21.4
- 1.21.3
- 1.21.2
- 1.21.1
- 1.21.0
- 1.20.4
- 1.20.3
- 1.20.2
- 1.20.1
- 1.20.0
- 1.19.1
- 1.19.0
- 1.18.0
- 1.17.2
- 1.17.1
- 1.17.0
- 1.16.3
- 1.16.2
- 1.16.1
- 1.16.0
- 1.15.9
- 1.15.8
- 1.15.7
- 1.15.6
- 1.15.5
- 1.15.4
- 1.15.3
- 1.15.2
- 1.15.1
- 1.15.0
- 1.14.2
- 1.14.1
- 1.14.0
- 1.13.10
- 1.13.9
- 1.13.8
- 1.13.7
- 1.13.6
- 1.13.5
- 1.13.4
- 1.13.3
- 1.13.2
- 1.13.1
- 1.13.0
- 1.12.1
- 1.12.0
- 1.11.0
- 1.10.0
- 1.9.0
- 1.8.2
- 1.8.1
- 1.7.1
- 1.7.0
- 1.6.5
- 1.6.4
- 1.6.3
- 1.6.2
- 1.6.1
- 1.5.2
- 1.5.1
- 1.4.10
- 1.4.9
- 1.4.8
- 1.4.7
- 1.4.6
- 1.4.5
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.3.2
- 1.3.1
- 1.2.15
- 1.2.14
- 1.2.13
- 1.2.12
- 1.2.11
- 1.2.8
- 1.2.7
- 1.2.6
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.1.14
- 1.1.13
- 1.1.12
- 1.1.11
- 1.1.10
- 1.1.9
- 1.1.8
- 1.1.7
- 1.1.6
- 1.1.5
- 1.1.4
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.23
- 1.0.22
- 1.0.21
- 1.0.20
- 1.0.19
- 1.0.18
- 1.0.17
- 1.0.16
- 1.0.15
- 1.0.14
- 1.0.13
- 1.0.12
- 1.0.11
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
This package is auto-updated.
Last update: 2026-05-15 11:06:57 UTC
README
Joona is a Laravel admin panel package with user management, roles and permissions, activity logging, and a Bootstrap 5–based UI layer. Unlike opinionated admin builders, it does not force predefined CRUD screens—you build your own Blade views and routes while reusing layouts, form helpers, and JavaScript components.
Requirements
- PHP 8.2+
- Laravel 11.x (recommended for new projects; the package can be added to existing apps)
- Node.js and npm (for compiling your Vite assets)
- Composer
The package ships database migrations. If your application already has tables with the same names (admin_users, roles, etc.), resolve naming conflicts before migrating.
Installation
1. Install the package
composer require codeartlv/joona
Laravel auto-discovers Codeart\Joona\Providers\JoonaProvider, which registers migrations and Artisan commands.
2. Publish assets and application provider
php artisan joona:publish
This command:
- Runs
npm installinside the package asset directory (vendor/codeartlv/joona/resources/assets) - Publishes flag images to
public/vendor/joona - Publishes
config/joona.php - Publishes
app/Providers/JoonaServiceProvider.php(extendsJoonaPanelProvider) - Registers
JoonaServiceProviderinbootstrap/providers.php(Laravel 11+)
On Laravel 10 and below, add App\Providers\JoonaServiceProvider::class to config/app.php manually if it was not added automatically.
3. Run migrations and seed
php artisan migrate php artisan joona:seed
Default login (change immediately in production):
| Field | Value |
|---|---|
| URL | /admin |
admin@localhost |
|
| Password | password |
The seeded password satisfies the default policy in config/joona.php (min:8,max:20,mixed,number,special). Stricter rules apply when users change passwords through the UI.
Frontend setup (Vite)
Joona does not ship pre-built CSS/JS for your app. You compile your own entry files and point the panel at them via addViteResources().
SCSS entry
Create e.g. resources/scss/admin.scss:
/* Theme variables (Bootstrap overrides) */ @import '@joona/scss/config.scss'; /* Your overrides here */ /* Package styles (Bootstrap, components, vendors) */ @import '@joona/scss/main.scss'; /* Your custom styles */
JavaScript entry
Create e.g. resources/js/admin.js:
import Joona from '@joona/js/main.js'; // Optional: register custom handlers // import Blog from './handlers/blog.js'; // Joona.addHandlers(Blog); Joona.ready();
main.js exports the runtime singleton as window.Joona. Call Joona.ready() once to bind data-bind handlers, load translations, and initialize built-in components.
Vite configuration
// vite.config.js import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import path from 'path'; export default defineConfig({ plugins: [ laravel({ input: ['resources/js/admin.js', 'resources/scss/admin.scss'], refresh: true, }), ], resolve: { alias: { '@joona': path.resolve(__dirname, 'vendor/codeartlv/joona/resources/assets'), '@joona-modules': path.resolve(__dirname, 'vendor/codeartlv/joona/resources/assets/node_modules'), }, }, });
Run npm install in your Laravel project, then build:
npm run build
# or during development:
npm run dev
Register Vite entries in the panel
Edit app/Providers/JoonaServiceProvider.php:
<?php namespace App\Providers; use Codeart\Joona\MetaData\Locale; use Codeart\Joona\MetaData\Page; use Codeart\Joona\Panel; use Codeart\Joona\Providers\JoonaPanelProvider; class JoonaServiceProvider extends JoonaPanelProvider { protected function configure(Panel $panel): void { $panel ->setBasePath('/admin') // optional, default is /admin ->setLocales([ new Locale('English', 'en', 'us'), ]) ->addViteResources([ 'resources/scss/admin.scss', 'resources/js/admin.js', ]) ->addPages([ Page::make('blog') ->route('blog.index') ->caption('Blog') ->icon('article'), ]); } }
Layouts load these files with @vite($vite_resources) in resources/views/global.blade.php.
Custom <head> / body snippets
Publish or create:
resources/views/vendor/joona/head.blade.phpresources/views/vendor/joona/body.blade.php
They are included from the main layout automatically.
Panel configuration
Configure the panel in JoonaServiceProvider::configure() using the Panel instance:
| Method | Description |
|---|---|
setBasePath(string $path) |
URL prefix for the admin area (default /admin) |
setBaseDomain(string $domain) |
Optional dedicated domain |
setAppName(string $name) |
Title shown in the UI |
setLogo(string $light, ?string $dark, ?string $icon) |
Logo URLs |
setLocales(Locale[] $locales) |
Language switcher entries |
useRolesAndPermissions(bool $state) |
Enable/disable RBAC (default true) |
addViteResources(array $paths) |
Vite entry files |
addPages(Page[] $pages) |
Sidebar navigation |
addPermissions(array $groups) |
Custom permission groups |
setPermissionLoader(string $class) |
Custom permission loader class |
addUserClasses(array $levels) |
Extra user level enums/classes |
addNotifications(array $classes) |
Notification handler classes |
addRoutes('free' | 'secure', callable|string $routes) |
Register route files or closures |
Navigation pages
use Codeart\Joona\MetaData\Page; Page::make('settings.blog') // nested ID: settings → blog ->route('blog.index') ->caption('Blog posts') ->icon('article') ->badge(3) // int or callable ->activeRoutes(['blog.edit', 'blog.create']);
Top-level IDs appear in the sidebar; dotted IDs nest under the first segment.
Locales
use Codeart\Joona\MetaData\Locale; new Locale('Latviešu', 'lv', 'lv'); // caption, locale code, flag file key
Flag SVGs are served from public/vendor/joona/images/flags/{map}.svg.
Authentication and guards
Joona merges an admin guard and joona user provider into config/auth.php:
- Guard:
admin(session driver) - Provider model:
Codeart\Joona\Models\User\AdminUser - Facade helper:
joona.authresolvesAuth::guard('admin')
Use Auth::guard('admin') or the joona.auth binding in your code. Do not confuse this with Laravel’s default web guard.
Routes and middleware
Package routes are registered under Panel::getBasePath() with the web and admin.web middleware groups. Authenticated package routes also use admin.auth.
| Middleware group | Purpose |
|---|---|
admin.web |
Locale, theme, activity logging |
admin.auth |
Requires admin login; optional permission checks |
userclass |
Alias for CheckUserClass middleware |
Adding your own admin routes
Register routes in JoonaServiceProvider::configure():
$panel->addRoutes('secure', function () { Route::get('/blog', [BlogController::class, 'index'])->name('blog.index'); }); $panel->addRoutes('free', function () { Route::get('/public-report', [ReportController::class, 'show'])->name('report.public'); });
Or pass a route file path:
$panel->addRoutes('secure', base_path('routes/admin.php'));
secure— wrapped inadmin.auth(must be logged in).free— onlyweb+admin.web(e.g. login pages you add yourself).
Equivalent manual grouping:
Route::middleware(['admin.web'])->group(function () { // Guest-accessible admin routes (theme, locale, etc.) Route::middleware(['admin.auth'])->group(function () { // Authenticated admin routes }); });
Named routes are available in JavaScript via Ziggy (@routes in the layout). Use the global route() helper the same way as in PHP.
Views and layouts
Page layout (<x-content>)
The content component renders a page title, optional sidebar, header controls, main area, and footer:
<x-content title="Page title"> <x-slot name="sidebar"> {{-- Filters, secondary nav --}} </x-slot> <x-slot name="controls"> {{-- Buttons in the page header --}} <x-button caption="Create" role="primary" icon="add" /> </x-slot> Main page content. <x-slot name="footer"> {{-- e.g. paginator --}} <x-paginator :total="$total" :size="25" /> </x-slot> </x-content>
- With a
sidebarslot, the sidebar layout is used. - Without it, the simple full-width layout is used.
Extending package layouts directly
@extends('joona::default') @section('content_main') ... @endsection
Other layouts: joona::global, joona::simple, joona::sidebar.
Blade directives
| Directive | Output |
|---|---|
@icon('name') |
Material Symbols icon <i> |
@attributes([...]) |
Renders HTML attributes via HtmlHelper::attributes() |
Blade components
Components are registered without a namespace prefix—use <x-form>, <x-button>, etc.
Forms
<x-form method="post" action="{{ route('blog.save') }}" class="my-form"> <div data-role="form.response"></div> <x-input name="title" label="Title" value="{{ $post->title }}" required /> <x-button caption="Save" role="primary" icon="check" /> </x-form>
The form renders data-bind="components.form" and submits via AJAX. Show feedback in an element with data-role="form.response" (created automatically if missing).
Field-level errors: use data-field="field_name" on a container, or rely on name matching inputs. Use * as the field key for global errors.
FormResponse (controller)
use Codeart\Joona\View\Components\Form\FormResponse; $form = new FormResponse(); $form->setSuccess('Data saved!'); $form->setError('Value required.', 'title'); // omit field or use '*' for global $form->setAction('reload', true); $form->setAction('redirect', '/admin/blog'); $form->setAction('close_popup', true); $form->setAction('reset', true); $form->addData('id', 1); return response()->json($form);
JSON shape: status (success|error), fields, message, actions, data.
Supported actions: redirect, reload, reset, close_popup, close_popup_reload.
Buttons
<x-button caption="Submit" role="primary" icon="check" type="submit" />
role maps to Bootstrap btn-{role}. Additional attributes are merged onto the <button> (default type="submit").
Alerts
<x-alert role="info"> Hello world </x-alert>
Roles follow Bootstrap alert variants (info, success, danger, etc.).
Dialog (modal content)
Use inside a modal loaded by the JS Modal class or as inline modal markup:
<x-form method="post" action="{{ route('blog.save') }}"> <x-dialog caption="Edit post"> <x-input name="title" label="Title" /> <x-slot name="footer"> <x-button caption="Save" icon="check" /> </x-slot> </x-dialog> </x-form>
Paginator
<x-paginator :total="$total" :size="25" param="page" :range="3" />
| Prop | Default | Description |
|---|---|---|
total |
0 |
Total row count |
size |
25 |
Rows per page |
param |
page |
Query string parameter |
range |
3 |
Page numbers shown around current |
links |
[] |
Custom link attributes; href can use sprintf with page number |
Hidden when only one page exists.
Input, textarea, select, checkbox
<x-input name="email" label="Email" value="{{ $user->email }}" size="md" icon-prepend="mail" required /> <x-textarea name="body" label="Body" /> <x-select name="status" label="Status" :options="$options" blank /> <x-checkbox name="active" label="Active" :checked="true" />
<x-select> options are Codeart\Joona\View\Components\Select\Option instances or Group objects for optgroups.
Datepicker, color picker, range
<x-datepicker name="published_at" label="Published" :value="$date" /> <x-colorpicker name="color" label="Color" /> <x-range name="priority" label="Priority" min="0" max="100" />
Pass extra data-* attributes for JS options (see component templates).
Password validator
<x-password-validator name="password" label="Password" :policy="config('joona.admin_password_policy')" />
Autocomplete
<x-autocomplete name="user_id" label="User" :value="$id" data-route="{{ route('users.search') }}" />
data-route is required (converted to route in JS). Optional: data-proxy, data-input.
Multiselect
<x-multiselect name="tags[]" label="Tags" type="checkbox" :options="$options" />
Tags (Tagify)
<x-tags label="Keywords" name="keywords" :value="$tags" data-search-url="{{ route('tags.search') }}" />
Uploader
<x-uploader name="files" class="default" :files="$existingFiles" data-uploadroute="files.upload" data-deleteroute="files.delete" data-limit="5" data-submitbtn="#save-button" />
Preload files using Codeart\Joona\View\Components\Uploader\File\Image or Document (extend UploadedFile). Return UploadResponse from upload/delete endpoints.
Optional: data-croproute, data-sortable, data-captions, crop presets via [data-role="crop-presets"] JSON.
Gallery
<x-gallery name="images" :items="$items" :sortable="true" />
Items should expose id, url, thumbnail for the template.
Editor (Editor.js)
<x-editor name="content" label="Content" :content="$blocks" />
Text editor (Pell)
<x-text-editor name="summary" label="Summary" :value="$html" />
Data table
<x-table sortable="handle"> <thead>...</thead> <tbody>...</tbody> </x-table>
Enables SortableJS when sortable is set (e.g. handle for drag handle selector).
Tree editor
<x-tree-editor :rows="$nodes" edit-route="categories.edit" sort-route="categories.sort" delete-route="categories.delete" :depth="3" />
Rows implement TreeNode (id, parentId, title, etc.).
Chart
<x-chart :data="$chartConfig" />
$chartConfig is passed to Chart.js as JSON in the template.
Map picker
<x-map-picker name="location" :value="$coordinates" class="map-picker-lg" />
Other components
| Component | Usage |
|---|---|
<x-accordion> |
Collapsible sections |
<x-offcanvas> |
Offcanvas panel (header/body/footer slots) |
<x-navbar> |
Tab-like nav |
<x-container> |
Width-constrained wrapper |
<x-copy> |
Copy-to-clipboard control |
<x-dropdown-radio> |
Radio options in a dropdown |
<x-table-bulk-options> |
Bulk actions for tables |
<x-page-footer-bar> |
Sticky footer actions |
<x-form-section-heading> |
Section title in long forms |
<x-checkbox-group> |
Grouped checkboxes |
<x-toast> |
Toast markup (usually driven by JS) |
JavaScript API
Runtime (Joona)
import Joona from '@joona/js/main.js'; Joona.ready(); // returns Promise Joona.addHandlers(MyHandler); Joona.init(contextElement); // bind new [data-bind] nodes (also runs after HTMX swaps if HTMX is present) const { instance } = await Joona.getInstance(element, 'components.uploader'); const { instance } = await Joona.getInstanceById('my-id'); const instances = await Joona.getInstances(element, 'blog.edit-form');
Translations: trans('joona::common.ok') and choice() after ready() (backed by lang.js).
Modal
import Modal from '@joona/js/components/modal.js'; const modal = new Modal(); await modal.open('/admin/users/edit/1', { animations: true }); await modal.close();
While open, window.JoonaModalInstance references the active modal.
Confirm dialog
import ConfirmDialog from '@joona/js/components/confirm-dialog.js'; new ConfirmDialog('Confirm', 'Delete this record?', [ { caption: 'Cancel', role: 'secondary', callback: () => {} }, { caption: 'Delete', role: 'primary', callback: () => { /* ... */ } }, ]).open();
Closes automatically when a button is pressed.
Custom JS handlers
Handlers group DOM behavior behind data-bind="plugin.action" attributes.
1. Create a handler class
// resources/js/handlers/blog.js import Handler from '@joona/js/handler.js'; export default class Blog extends Handler { static get pluginName() { return 'blog'; } editForm(element, parameters, runtime) { // element: DOM node // parameters: other data-* attributes (data-id → id) // runtime: Joona instance } }
edit-form in HTML maps to the editForm method.
2. Register and bind
// resources/js/admin.js import Joona from '@joona/js/main.js'; import Blog from './handlers/blog.js'; Joona.addHandlers(Blog); Joona.ready();
<div data-bind="blog.edit-form" data-id="1"></div>
3. Return instances for cross-component access
userComponent(element, parameters, runtime) { return { hello(name) { alert(name); }, }; } editForm(element, parameters, runtime) { runtime.getInstance(element, 'blog.user-component').then(({ instance }) => { instance.hello('Bob'); }); }
Return a plain object or a Promise from handler methods to store retrievable instances.
Built-in components handler
These data-bind values are handled automatically:
| Bind | Component |
|---|---|
components.form |
AJAX form |
components.uploader |
File uploader |
components.autocomplete |
Autocomplete |
components.datepicker |
Date picker |
components.multi-select |
Multiselect dropdown |
components.passwordValidator |
Password strength |
components.map-picker |
Leaflet map |
components.gallery |
Image gallery |
components.chart |
Chart.js |
components.tree-editor |
Nested sortable tree |
components.editor |
Editor.js |
components.text-editor |
Pell editor |
components.table |
Sortable table |
components.tags |
Tagify |
components.copy-text |
Clipboard copy |
components.table-bulk-options |
Bulk table actions |
The admin handler covers panel UI (theme switch, sidebar, notifications, profile modal, etc.).
Permissions
When useRolesAndPermissions(true):
- Routes can be tied to permissions via
RoutePermissioninsidePermissionGroupobjects. - Menu items hide automatically when
Gate::denies()the page route. - Middleware
CheckPermissionsruns onadmin.authroutes.
Register custom permissions in configure():
use Codeart\Joona\Auth\Permissions\PermissionGroup; use Codeart\Joona\Auth\Permissions\RoutePermission; $panel->addPermissions([ PermissionGroup::make('Blog', [ new RoutePermission( id: 'blog_edit', routes: ['blog.edit', 'blog.save'], label: 'Edit blog posts', ), ]), ]);
Configuration (config/joona.php)
| Key | Description |
|---|---|
admin_password_policy |
Comma-separated rules: min, max, uppercase, lowercase, mixed, number, special |
js_translations |
Extra translation keys exposed to JavaScript |
auto_block_user |
Failed login attempts before lockout (0 = disabled) |
class_role_mode |
interchangeable — user class vs role behavior |
Artisan commands
| Command | Description |
|---|---|
joona:publish |
Install npm deps in package assets, publish config/images/provider |
joona:seed |
Seed default admin user (AdminUserSeeder) |
joona:update-session |
Updates admin session records (scheduled every 10 minutes) |
Package structure (reference)
config/ joona.php, auth guard merge
database/migrations/ Admin users, roles, permissions, logs, notifications
export/ Published JoonaServiceProvider stub
resources/
assets/ SCSS, JS, npm package (Bootstrap, Chart.js, Editor.js, …)
views/ Layouts and Blade components
routes/ web.php (guest), secure.php (authenticated)
src/ PHP: Panel, HTTP, Auth, View components, Commands
Notes
- The package adds an
adminguard andjoonaprovider—plan naming if you add more guards. - Static images (logos, flags) live under
public/vendor/joonaafter publish. - For HTMX projects, the runtime re-initializes bindings on
htmx:afterSwap/htmx:oobAfterSwapwhen HTMX is loaded in your app (not bundled with Joona). - Override package views by placing files in
resources/views/vendor/joona/.