NexusCMS: Module Manager y su nuevo Admin
Como funciona el nuevo Admin del Module Manager en NexusCMS y por que acelera el desarrollo de modulos
En NexusCMS acabamos de consolidar una mejora clave en el Module Manager: ahora los modulos pueden integrarse al panel de administracion de dos formas claras y predecibles.
Que hay de nuevo en el Admin
La novedad importante es que el Module Manager entiende el campo admin_crud dentro de cada module.json y activa el flujo adecuado:
json: CRUD administrativo generico basado en configuracioncustom: rutas y vistas administrativas propias del modulo
Esto nos da un equilibrio muy util: velocidad para casos comunes y flexibilidad total para casos complejos.
Modo 1: Admin rapido con json
Cuando un modulo define admin_crud: "json", todo el flujo CRUD se auto-registra sin escribir una sola ruta. Veamos como funciona paso a paso.
Paso 1: Configuracion en module.json
Defines la config JSON y el menu en un unico lugar:
{
"name": "Store",
"enabled": true,
"routes": true,
"migrations": true,
"views": true,
"admin_crud": "json",
"admin_menu": {
"label": "Store",
"icon": "fas fa-shopping-cart",
"route": "admin.store.crud.index",
"order": 25
}
}
El campo admin_crud: "json" es la clave que dispara todo el mecanismo.
Paso 2: ModuleServiceProvider auto-registra las rutas
En el boot de ModuleServiceProvider, el sistema detecta todos los modulos con admin_crud: "json" e inyecta 6 rutas automaticamente:
// Genera esto internamente:
Route::middleware(['web', 'auth', 'permission:access.admin.panel'])
->prefix('acp/store/manage')
->name('admin.store.crud.')
->group(function () {
// GET /acp/store/manage
Route::get('/', [GenericCrudController::class, 'index'])->name('index');
// GET /acp/store/manage/create
Route::get('/create', [GenericCrudController::class, 'create'])->name('create');
// POST /acp/store/manage (guardar nuevo)
Route::post('/', [GenericCrudController::class, 'store'])->name('store');
// GET /acp/store/manage/{id}/edit
Route::get('/{id}/edit', [GenericCrudController::class, 'edit'])->name('edit');
// PUT /acp/store/manage/{id} (actualizar)
Route::put('/{id}', [GenericCrudController::class, 'update'])->name('update');
// DELETE /acp/store/manage/{id}
Route::delete('/{id}', [GenericCrudController::class, 'destroy'])->name('destroy');
});
Sin tocar el modulo, ya tienes CRUD completo protegido por auth y permisos.
Paso 3: GenericCrudController interpreta la config
Cada accion del controller (index, create, store, etc) lee la config del modulo y adapta el comportamiento:
class GenericCrudController extends Controller
{
public function index(Request $request, string $module)
{
// Obtiene la config del modulo
$config = ModuleRegistryService::getModuleConfig($module);
// Valida que tenga lista y columnas definidas
$listConfig = $config['list'] ?? [];
// Ejecuta busquedas, paginacion, etc segun la config
$records = $this->buildQuery($config)
->paginate(15);
// Retorna la vista generica con datos
return view('admin::generic-crud.index', [
'config' => $config,
'records' => $records,
'base' => "admin.{$module}.crud.",
]);
}
}
El controller NO sabe de Store especifico, solo interpreta la configuracion dinamicamente.
Paso 4: Vista generica renderiza tabla + formularios
La misma vista admin::generic-crud.index se usa para todos los modulos JSON. Renderiza:
- Encabezado con titulo de la config
- Campo de busqueda si
searchable: true - Tabla con columnas segun
list.columns - Botones de editar/eliminar con rutas dinamicas
- Paginacion
<table>
<thead>
@foreach($config['list']['columns'] as $col)
<th>{{ $col['label'] ?? $col['field'] }}</th>
@endforeach
</thead>
<tbody>
@foreach($records as $record)
<tr>
@foreach($config['list']['columns'] as $col)
<td>{{ $record->{$col['field']} }}</td>
@endforeach
<td>
<a href="{{ route($base.'edit', $record->id) }}">Edit</a>
<form action="{{ route($base.'destroy', $record->id) }}" method="POST">
<button type="submit">Delete</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
Todo esta parametrizado. Una sola vista sirve para Store, Products, Categories, etc.
Ejemplo real: Store Products en NexusCMS
En el modulo Store de NexusCMS, el archivo admin-crud.json define todo el CRUD para productos:
{
"title": "Store Products",
"model": "Modules\\Store\\Domain\\Models\\StoreProduct",
"list": {
"searchable": true,
"search_columns": ["name", "key", "type"],
"order_by": "sort_order",
"order_dir": "asc",
"columns": [
{ "field": "sort_order", "label": "#" },
{ "field": "name", "label": "Product" },
{ "field": "key", "label": "Key" },
{ "field": "cost", "label": "Cost (DP)" },
{ "field": "active", "label": "Active", "format": "boolean" }
]
},
"form": {
"sections": [
{
"title": "Product Info",
"fields": [
{ "name": "name", "label": "Name", "type": "text", "required": true },
{ "name": "key", "label": "Key", "type": "text", "required": true },
{ "name": "description", "label": "Description", "type": "textarea" }
]
},
{
"title": "Pricing & Settings",
"fields": [
{ "name": "cost", "label": "Cost (DP)", "type": "number", "required": true },
{ "name": "active", "label": "Active", "type": "checkbox" }
]
}
]
}
}
Mira lo que sucede:
En la lista:
- GenericCrudController lee
list.columnsy renderiza exactamente esas 5 columnas - El campo
search_columnshabilita busqueda en name, key y type order_by: "sort_order"ordena por numero de posicion- El formato
"boolean"renderiza checkboxes
En el formulario:
- Se crean 2 secciones (Product Info, Pricing & Settings)
- Cada seccion agrupa campos relacionados
- El controller valida
required: trueantes de guardar - Los tipos (text, textarea, number, checkbox) determinan el input HTML
Sin escribir rutas, controller ni vistas.
Solo este JSON genera:
- Listado con busqueda y ordenamiento
- Formulario en 2 secciones
- Validacion de campos obligatorios
- Guardado y actualizacion de datos
Todo parametrizado, nada hardcodeado.
Por que esto es potente
- Zero boilerplate - Una config JSON reemplaza 200+ lineas de codigo
- Consistente - Todos los modulos JSON usan la misma UI
- Escalable - Si cambias la vista generica, todos se benefician
- Flexible - El
custommode existe para casos que necesiten mas control
Modo 2: Admin a medida con custom
Si el modulo necesita una UX especifica o logica mas avanzada, usamos admin_crud: "custom".
{
"name": "Donate",
"admin_crud": "custom",
"admin_menu": {
"label": "Donations",
"route": "admin.donate.plans.index"
}
}
En este modo, el provider base del modulo carga automaticamente:
Admin/routes.phpAdmin/views
Asi mantenemos el mismo punto de entrada del Module Manager, pero con total control sobre el panel.
Por que esto mejora el desarrollo
Lo mejor de este cambio es que define un contrato simple para todos los modulos:
- Si quieres ir rapido:
json - Si necesitas algo avanzado:
custom
No hay que pelear con convenciones ambiguas. El equipo sabe exactamente como conectar cada modulo al Admin y escalarlo despues sin rehacer todo.
Lo que viene
Nuestro siguiente paso es seguir estandarizando configuraciones para que mas modulos puedan arrancar en modo json desde el dia uno, y saltar a custom solo cuando de verdad haga falta.
La evolucion del Module Manager va en esa direccion: menos friccion, mas consistencia, mejor Admin.