Compare commits
2 Commits
b07e802f78
...
be1ac25047
| Author | SHA1 | Date | |
|---|---|---|---|
| be1ac25047 | |||
| 7fd5b0c3a0 |
@@ -9,9 +9,9 @@ APP_PORT=8080
|
||||
SEED_DEV_DATA=false
|
||||
RUN_DB_SEED_ON_FIRST_START=true
|
||||
ENSURE_INITIAL_ADMIN_ON_EMPTY_DB=true
|
||||
INITIAL_ADMIN_NAME=Administrator
|
||||
INITIAL_ADMIN_EMAIL=info@termanager.it
|
||||
INITIAL_ADMIN_PASSWORD=Password123!
|
||||
INITIAL_ADMIN_NAME=
|
||||
INITIAL_ADMIN_EMAIL=
|
||||
INITIAL_ADMIN_PASSWORD=
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mariadb
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,6 +2,8 @@
|
||||
/node_modules/
|
||||
/.env
|
||||
/storage/*.key
|
||||
/storage/app/.app_key
|
||||
/storage/app/.db_seeded
|
||||
/storage/logs/
|
||||
/storage/framework/
|
||||
/bootstrap/cache/
|
||||
@@ -13,6 +15,7 @@
|
||||
/.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*.sql
|
||||
docker-compose.override.yml
|
||||
db_data/
|
||||
redis_data/
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
@@ -19,10 +22,8 @@ class CreateInitialAdmin extends Command
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
if (User::count() > 0) {
|
||||
$this->info('Users already exist. Skipping initial admin creation.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
// Always ensure roles/permissions are present before assigning roles.
|
||||
Artisan::call('db:seed', ['--class' => RolesAndPermissionsSeeder::class, '--force' => true]);
|
||||
|
||||
$name = (string) ($this->option('name') ?? '');
|
||||
$email = (string) ($this->option('email') ?? '');
|
||||
@@ -45,6 +46,30 @@ class CreateInitialAdmin extends Command
|
||||
$password = $password !== '' ? $password : (string) $this->secret('Password amministratore (min 8 caratteri)');
|
||||
}
|
||||
|
||||
if (User::count() > 0) {
|
||||
$existingAdmin = User::role('amministratore')->first();
|
||||
if ($existingAdmin) {
|
||||
$this->info('An administrator already exists. Skipping initial admin creation.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
if ($email !== '') {
|
||||
$existingUser = User::where('email', $email)->first();
|
||||
if ($existingUser) {
|
||||
$existingUser->assignRole('amministratore');
|
||||
$this->info("Granted admin role to existing user: {$existingUser->email}");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
$firstUser = User::query()->oldest('id')->first();
|
||||
if ($firstUser) {
|
||||
$firstUser->assignRole('amministratore');
|
||||
$this->warn("No admin role found. Granted admin role to first existing user: {$firstUser->email}");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
$validator = Validator::make([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
@@ -63,13 +88,17 @@ class CreateInitialAdmin extends Command
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$admin = User::create([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'password' => Hash::make($password),
|
||||
]);
|
||||
$admin = DB::transaction(function () use ($name, $email, $password) {
|
||||
$user = User::create([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'password' => Hash::make($password),
|
||||
]);
|
||||
|
||||
$admin->assignRole('amministratore');
|
||||
$user->assignRole('amministratore');
|
||||
|
||||
return $user;
|
||||
});
|
||||
|
||||
$this->info("Initial admin created: {$admin->email}");
|
||||
|
||||
|
||||
60
app/Livewire/Settings/UsersIndex.php
Normal file
60
app/Livewire/Settings/UsersIndex.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Component;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
class UsersIndex extends Component
|
||||
{
|
||||
public string $name = '';
|
||||
public string $email = '';
|
||||
public string $password = '';
|
||||
public string $password_confirmation = '';
|
||||
public array $selectedPermissions = [];
|
||||
public array $availablePermissions = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->availablePermissions = Permission::query()
|
||||
->orderBy('name')
|
||||
->pluck('name')
|
||||
->all();
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')],
|
||||
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||
'selectedPermissions' => ['array'],
|
||||
'selectedPermissions.*' => ['string', Rule::in($this->availablePermissions)],
|
||||
];
|
||||
}
|
||||
|
||||
public function createUser(): void
|
||||
{
|
||||
$validated = $this->validate();
|
||||
|
||||
$user = User::create([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'password' => $validated['password'],
|
||||
]);
|
||||
|
||||
$user->syncPermissions($validated['selectedPermissions'] ?? []);
|
||||
|
||||
$this->reset(['name', 'email', 'password', 'password_confirmation', 'selectedPermissions']);
|
||||
session()->flash('success', 'Utente creato con successo.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings.users-index', [
|
||||
'users' => User::query()->with('roles', 'permissions')->orderBy('name')->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,7 @@ retry 10 3 php artisan migrate --force
|
||||
# -----------------------------------------------
|
||||
# 7b. Seed database on first container startup only
|
||||
# -----------------------------------------------
|
||||
SEED_MARKER_FILE="/var/www/html/storage/app/.db_seeded"
|
||||
SEED_MARKER_FILE="/var/www/html/storage/framework/.runtime_db_seeded"
|
||||
RUN_DB_SEED_ON_FIRST_START="${RUN_DB_SEED_ON_FIRST_START:-true}"
|
||||
|
||||
if [ "$RUN_DB_SEED_ON_FIRST_START" = "true" ]; then
|
||||
|
||||
@@ -114,6 +114,11 @@
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||||
Impostazioni
|
||||
</a>
|
||||
<a href="{{ route('users.index') }}"
|
||||
class="flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs('users.*') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-100' }}">
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||||
Utenti
|
||||
</a>
|
||||
<a href="{{ route('zone.index') }}"
|
||||
class="flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs('zone.*') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-100' }}">
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||||
|
||||
101
resources/views/livewire/settings/users-index.blade.php
Normal file
101
resources/views/livewire/settings/users-index.blade.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Utenti</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">Crea utenti e assegna i permessi applicativi.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">Nuovo utente</h2>
|
||||
|
||||
<form wire:submit="createUser" class="space-y-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700">Nome *</label>
|
||||
<input wire:model="name" id="name" type="text" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm">
|
||||
@error('name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Email *</label>
|
||||
<input wire:model="email" id="email" type="email" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm">
|
||||
@error('email') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700">Password *</label>
|
||||
<input wire:model="password" id="password" type="password" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm">
|
||||
@error('password') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password_confirmation" class="block text-sm font-medium text-gray-700">Conferma Password *</label>
|
||||
<input wire:model="password_confirmation" id="password_confirmation" type="password" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="block text-sm font-medium text-gray-700 mb-2">Permessi utente</p>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
@foreach($availablePermissions as $permission)
|
||||
<label class="flex items-center gap-2 rounded border border-gray-200 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50">
|
||||
<input wire:model="selectedPermissions" type="checkbox" value="{{ $permission }}" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span>{{ $permission }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
@error('selectedPermissions.*') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 transition">Crea Utente</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">Utenti esistenti</h2>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left font-medium text-gray-600">Nome</th>
|
||||
<th class="px-4 py-2 text-left font-medium text-gray-600">Email</th>
|
||||
<th class="px-4 py-2 text-left font-medium text-gray-600">Ruoli</th>
|
||||
<th class="px-4 py-2 text-left font-medium text-gray-600">Permessi diretti</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
@forelse($users as $user)
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-gray-900">{{ $user->name }}</td>
|
||||
<td class="px-4 py-2 text-gray-700">{{ $user->email }}</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@forelse($user->roles as $role)
|
||||
<span class="inline-flex items-center rounded-full bg-indigo-50 px-2 py-0.5 text-xs font-medium text-indigo-700">{{ $role->name }}</span>
|
||||
@empty
|
||||
<span class="text-xs text-gray-400">-</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@forelse($user->permissions as $permission)
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700">{{ $permission->name }}</span>
|
||||
@empty
|
||||
<span class="text-xs text-gray-400">-</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-6 text-center text-gray-400">Nessun utente trovato</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,6 +24,7 @@ use App\Livewire\AuditLog;
|
||||
use App\Livewire\Settings\SettingsEdit;
|
||||
use App\Livewire\Settings\ZoneIndex;
|
||||
use App\Livewire\Settings\TipologieIndex;
|
||||
use App\Livewire\Settings\UsersIndex;
|
||||
use App\Livewire\Privacy;
|
||||
|
||||
/*
|
||||
@@ -101,6 +102,7 @@ Route::middleware('auth')->group(function () {
|
||||
// Settings (admin)
|
||||
Route::middleware('permission:settings.manage')->group(function () {
|
||||
Route::get('impostazioni', SettingsEdit::class)->name('settings.edit');
|
||||
Route::get('utenti', UsersIndex::class)->name('users.index');
|
||||
Route::get('zone', ZoneIndex::class)->name('zone.index');
|
||||
Route::get('tipologie', TipologieIndex::class)->name('tipologie.index');
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
base64:7ycOQwH6FjKdElpvJW9JU33pxtNAbOHxGhj6s930X+U=
|
||||
Reference in New Issue
Block a user