new admin design with mnodules

This commit is contained in:
Mark Gregory
2026-07-24 09:35:18 +01:00
parent 5e9f95a5b4
commit c2a19a16e6
28 changed files with 205 additions and 42 deletions
@@ -0,0 +1,38 @@
<?php
namespace Modules\Administration\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Schema;
use Modules\Audit\Models\AuditLog;
use Modules\Media\Models\Media;
use Spatie\Permission\Models\Role;
final class DashboardController extends Controller
{
public function __invoke()
{
return view('administration::dashboard', [
'userCount' => User::count(),
'roleCount' => Schema::hasTable('roles') ? Role::count() : 0,
'mediaCount' => Schema::hasTable('media') ? Media::count() : 0,
'auditCount' => Schema::hasTable('audit_logs') ? AuditLog::count() : 0,
'recentAudit' => Schema::hasTable('audit_logs') ? AuditLog::query()->latest('created_at')->limit(8)->get() : collect(),
'modules' => $this->modules(),
]);
}
private function modules(): array
{
$statuses = json_decode((string) file_get_contents(base_path('modules_statuses.json')), true) ?: [];
$modules = [];
foreach (glob(base_path('Modules/*/module.json')) ?: [] as $manifest) {
$data = json_decode((string) file_get_contents($manifest), true) ?: [];
$name = $data['name'] ?? basename(dirname($manifest));
$modules[] = ['name' => $name, 'version' => $data['version'] ?? '1.0.0', 'enabled' => (bool) ($statuses[$name] ?? false)];
}
usort($modules, fn ($a, $b) => strcasecmp($a['name'], $b['name']));
return $modules;
}
}
@@ -0,0 +1,26 @@
<?php
namespace Modules\Administration\Http\Controllers;
use App\Http\Controllers\Controller;
final class ModuleController extends Controller
{
public function index()
{
$statuses = json_decode((string) file_get_contents(base_path('modules_statuses.json')), true) ?: [];
$modules = collect(glob(base_path('Modules/*/module.json')) ?: [])->map(function (string $manifest) use ($statuses): array {
$data = json_decode((string) file_get_contents($manifest), true) ?: [];
$name = $data['name'] ?? basename(dirname($manifest));
return [
'name' => $name,
'description' => $data['description'] ?? 'Laravel application module.',
'version' => $data['version'] ?? '1.0.0',
'enabled' => (bool) ($statuses[$name] ?? false),
'providers' => $data['providers'] ?? [],
];
})->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)->values();
return view('administration::modules.index', compact('modules'));
}
}
@@ -0,0 +1,14 @@
<x-layouts.app :title="__('Administration')">
<div class="space-y-6">
<div><h1 class="text-2xl font-semibold text-zinc-950 dark:text-zinc-100">Administration</h1><p class="mt-1 text-sm text-zinc-600 dark:text-zinc-400">Platform health and recent activity.</p></div>
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
@foreach([['Users',$userCount,'users'],['Roles',$roleCount,'shield-check'],['Media',$mediaCount,'photo'],['Audit events',$auditCount,'clipboard-document-list']] as [$label,$count,$icon])
<div class="rounded-xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><flux:icon :name="$icon" class="size-5 text-zinc-500"/><div class="mt-4 text-3xl font-semibold">{{ $count }}</div><div class="text-sm text-zinc-500">{{ $label }}</div></div>
@endforeach
</div>
<div class="grid gap-6 xl:grid-cols-2">
<div class="rounded-xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><div class="border-b border-zinc-200 px-5 py-4 font-semibold dark:border-zinc-700">Modules</div><div class="divide-y divide-zinc-200 dark:divide-zinc-800">@foreach($modules as $module)<div class="flex items-center justify-between px-5 py-3"><div><div class="font-medium">{{ $module['name'] }}</div><div class="text-xs text-zinc-500">v{{ $module['version'] }}</div></div><span class="rounded-full px-2.5 py-1 text-xs {{ $module['enabled'] ? 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200' : 'bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300' }}">{{ $module['enabled'] ? 'Enabled' : 'Disabled' }}</span></div>@endforeach</div></div>
<div class="rounded-xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><div class="border-b border-zinc-200 px-5 py-4 font-semibold dark:border-zinc-700">Recent audit activity</div><div class="divide-y divide-zinc-200 dark:divide-zinc-800">@forelse($recentAudit as $log)<div class="px-5 py-3"><div class="font-medium">{{ $log->event }}</div><div class="text-xs text-zinc-500">{{ optional($log->created_at)->diffForHumans() }}</div></div>@empty<div class="px-5 py-8 text-center text-sm text-zinc-500">No audit events yet.</div>@endforelse</div></div>
</div>
</div>
</x-layouts.app>
@@ -0,0 +1 @@
<x-layouts.app :title="__('Modules')"><div class="space-y-6"><div><h1 class="text-2xl font-semibold">Modules</h1><p class="mt-1 text-sm text-zinc-500">Installed platform modules and runtime status.</p></div><div class="overflow-hidden rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900"><table class="min-w-full divide-y divide-zinc-200 text-sm dark:divide-zinc-700"><thead class="bg-zinc-50 text-left text-xs uppercase text-zinc-500 dark:bg-zinc-950"><tr><th class="px-4 py-3">Module</th><th class="px-4 py-3">Version</th><th class="px-4 py-3">Status</th><th class="px-4 py-3">Providers</th></tr></thead><tbody class="divide-y divide-zinc-200 dark:divide-zinc-800">@foreach($modules as $module)<tr><td class="px-4 py-3"><div class="font-medium">{{ $module['name'] }}</div><div class="text-xs text-zinc-500">{{ $module['description'] }}</div></td><td class="px-4 py-3">{{ $module['version'] }}</td><td class="px-4 py-3">{{ $module['enabled'] ? 'Enabled' : 'Disabled' }}</td><td class="px-4 py-3 text-xs text-zinc-500">{{ implode(', ', $module['providers']) ?: '—' }}</td></tr>@endforeach</tbody></table></div></div></x-layouts.app>
+6 -1
View File
@@ -1,5 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Administration\Http\Controllers\DashboardController;
use Modules\Administration\Http\Controllers\ModuleController;
// Module routes are intentionally registered by each feature as it is enabled.
Route::middleware(['auth', 'verified'])->prefix('administration')->name('administration.')->group(function (): void {
Route::get('/', DashboardController::class)->name('dashboard');
Route::get('/modules', [ModuleController::class, 'index'])->name('modules.index');
});