@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Administration;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Administration\StoreTenantRequest;
|
||||
use App\Http\Requests\Administration\UpdateTenantRequest;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Services\TenantProvisioningService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class TenantController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
{
|
||||
$this->authorize('viewAny', Tenant::class);
|
||||
|
||||
$tenants = Tenant::query()
|
||||
->withCount('users')
|
||||
->with('installations:id,tenant_id,deployment_type,domain,status,last_seen_at')
|
||||
->orderBy('name')
|
||||
->paginate(15)
|
||||
->withQueryString();
|
||||
|
||||
return Inertia::render('administration/tenants/index', ['tenants' => $tenants]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$this->authorize('create', Tenant::class);
|
||||
|
||||
return Inertia::render('administration/tenants/create');
|
||||
}
|
||||
|
||||
public function store(StoreTenantRequest $request, TenantProvisioningService $service): RedirectResponse
|
||||
{
|
||||
$tenant = $service->createWithOwner($request->validated());
|
||||
|
||||
return to_route('administration.tenants.edit', $tenant)
|
||||
->with('success', 'Tenant dan Tenant Owner berhasil dibuat.');
|
||||
}
|
||||
|
||||
public function edit(Tenant $tenant): Response
|
||||
{
|
||||
$this->authorize('update', $tenant);
|
||||
|
||||
$users = User::query()
|
||||
->withoutGlobalScope('tenant')
|
||||
->where('tenant_id', $tenant->id)
|
||||
->orderBy('name')
|
||||
->get(['uuid', 'name', 'email', 'is_active', 'created_at']);
|
||||
|
||||
return Inertia::render('administration/tenants/edit', [
|
||||
'tenant' => $tenant,
|
||||
'users' => $users,
|
||||
'installation' => $tenant->installations()->first(['id', 'deployment_type', 'domain', 'status', 'last_seen_at']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateTenantRequest $request, Tenant $tenant): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $tenant);
|
||||
$data = $request->validated();
|
||||
$tenant->update(collect($data)->only(['name', 'slug', 'is_active'])->all());
|
||||
if (isset($data['deployment_type'])) {
|
||||
$tenant->installations()->first()?->update([
|
||||
'deployment_type' => $data['deployment_type'],
|
||||
'domain' => $data['deployment_domain'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Tenant berhasil diperbarui.');
|
||||
}
|
||||
|
||||
public function destroy(Tenant $tenant): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $tenant);
|
||||
abort_if($tenant->users()->exists(), 422, 'Tenant yang masih memiliki user tidak dapat dihapus. Nonaktifkan tenant sebagai gantinya.');
|
||||
$tenant->delete();
|
||||
|
||||
return to_route('administration.tenants.index')->with('success', 'Tenant berhasil dihapus.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Administration;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Administration\ResetUserPasswordRequest;
|
||||
use App\Http\Requests\Administration\StoreUserRequest;
|
||||
use App\Http\Requests\Administration\UpdateUserRequest;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(Tenant $tenant): Response
|
||||
{
|
||||
abort_unless(request()->user()->can('user.view'), 403);
|
||||
|
||||
$users = User::query()
|
||||
->with('roles:id,name')
|
||||
->when(! request()->user()->is_platform_admin, fn ($query) => $query->role(SystemRole::TenantUser->value))
|
||||
->orderBy('name')
|
||||
->paginate(15)
|
||||
->withQueryString();
|
||||
|
||||
return Inertia::render('administration/users/index', compact('tenant', 'users'));
|
||||
}
|
||||
|
||||
public function create(Tenant $tenant): Response
|
||||
{
|
||||
abort_unless(request()->user()->can('user.create'), 403);
|
||||
|
||||
return Inertia::render('administration/users/create', ['tenant' => $tenant]);
|
||||
}
|
||||
|
||||
public function store(StoreUserRequest $request, Tenant $tenant): RedirectResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
$user = User::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => $data['password'],
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
]);
|
||||
$user->forceFill(['email_verified_at' => now()])->save();
|
||||
$user->assignRole(SystemRole::TenantUser->value);
|
||||
|
||||
return to_route('administration.users.index', $tenant)->with('success', 'User berhasil dibuat.');
|
||||
}
|
||||
|
||||
public function edit(Tenant $tenant, User $user): Response
|
||||
{
|
||||
$this->ensureManageableUser($tenant, $user);
|
||||
abort_unless(request()->user()->can('user.update'), 403);
|
||||
|
||||
return Inertia::render('administration/users/edit', [
|
||||
'tenant' => $tenant,
|
||||
'managedUser' => $user->load('roles:id,name'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateUserRequest $request, Tenant $tenant, User $user): RedirectResponse
|
||||
{
|
||||
$this->ensureManageableUser($tenant, $user);
|
||||
$data = $request->validated();
|
||||
$user->update(['name' => $data['name'], 'email' => $data['email']]);
|
||||
|
||||
return back()->with('success', 'User berhasil diperbarui.');
|
||||
}
|
||||
|
||||
public function resetPassword(ResetUserPasswordRequest $request, Tenant $tenant, User $user): RedirectResponse
|
||||
{
|
||||
$this->ensureManageableUser($tenant, $user);
|
||||
$user->update(['password' => $request->validated('password')]);
|
||||
$user->tokens()->delete();
|
||||
|
||||
return back()->with('success', 'Password user berhasil direset dan token API dicabut.');
|
||||
}
|
||||
|
||||
public function toggleActive(Tenant $tenant, User $user): RedirectResponse
|
||||
{
|
||||
$this->ensureManageableUser($tenant, $user);
|
||||
abort_unless(request()->user()->can('user.update'), 403);
|
||||
abort_if(request()->user()->is($user), 422, 'Anda tidak dapat menonaktifkan akun sendiri.');
|
||||
abort_if($user->is_active && $user->hasRole(SystemRole::TenantAdmin->value), 422, 'Akun Tenant Admin utama tidak dapat dinonaktifkan dari pengelolaan user tenant.');
|
||||
$user->update(['is_active' => ! $user->is_active]);
|
||||
$user->tokens()->delete();
|
||||
|
||||
return back()->with('success', $user->is_active ? 'User diaktifkan.' : 'User dinonaktifkan.');
|
||||
}
|
||||
|
||||
public function destroy(Tenant $tenant, User $user): RedirectResponse
|
||||
{
|
||||
$this->ensureManageableUser($tenant, $user);
|
||||
abort_unless(request()->user()->can('user.delete'), 403);
|
||||
abort_if(request()->user()->is($user), 422, 'Anda tidak dapat menghapus akun sendiri.');
|
||||
abort_if($user->hasRole(SystemRole::TenantAdmin->value), 422, 'Akun Tenant Admin utama tidak dapat dihapus dari pengelolaan user tenant.');
|
||||
$user->delete();
|
||||
|
||||
return to_route('administration.users.index', $tenant)->with('success', 'User berhasil dihapus.');
|
||||
}
|
||||
|
||||
private function ensureManageableUser(Tenant $tenant, User $user): void
|
||||
{
|
||||
abort_unless($user->tenant_id === $tenant->id && ! $user->is_platform_admin, 404);
|
||||
abort_if(
|
||||
! request()->user()->is_platform_admin && ! $user->hasRole(SystemRole::TenantUser->value),
|
||||
403,
|
||||
'Tenant Admin hanya dapat mengelola Tenant User.',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreTenantRequest;
|
||||
use App\Http\Requests\UpdateTenantRequest;
|
||||
use App\Http\Resources\TenantResource;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class TenantController extends Controller
|
||||
{
|
||||
public function index(): AnonymousResourceCollection
|
||||
{
|
||||
$this->authorize('viewAny', Tenant::class);
|
||||
$user = request()->user();
|
||||
$query = Tenant::query()->orderBy('name');
|
||||
|
||||
if (! ($user->is_platform_admin && $user->can('tenant.access-any'))) {
|
||||
$query->whereKey($user->tenant_id);
|
||||
}
|
||||
|
||||
return TenantResource::collection($query->paginate());
|
||||
}
|
||||
|
||||
public function store(StoreTenantRequest $request): JsonResponse
|
||||
{
|
||||
$tenant = Tenant::create($request->validated());
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Tenant created successfully', 'data' => new TenantResource($tenant)], 201);
|
||||
}
|
||||
|
||||
public function show(Tenant $tenant): JsonResponse
|
||||
{
|
||||
$this->authorize('view', $tenant);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Tenant retrieved successfully', 'data' => new TenantResource($tenant)]);
|
||||
}
|
||||
|
||||
public function update(UpdateTenantRequest $request, Tenant $tenant): JsonResponse
|
||||
{
|
||||
$this->authorize('update', $tenant);
|
||||
$tenant->update($request->validated());
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Tenant updated successfully', 'data' => new TenantResource($tenant)]);
|
||||
}
|
||||
|
||||
public function destroy(Tenant $tenant): JsonResponse
|
||||
{
|
||||
$this->authorize('delete', $tenant);
|
||||
$tenant->delete();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Tenant deleted successfully', 'data' => null]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\LicenseStatus;
|
||||
use App\Enums\SystemRole;
|
||||
use App\Models\DeploymentInstallation;
|
||||
use App\Models\Device;
|
||||
use App\Models\License;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function __invoke(): Response
|
||||
{
|
||||
$user = request()->user();
|
||||
|
||||
if ($user->is_platform_admin) {
|
||||
return Inertia::render('dashboard', [
|
||||
'role' => SystemRole::MasterAdmin->value,
|
||||
'tenant' => null,
|
||||
'stats' => [
|
||||
'tenants' => Tenant::count(),
|
||||
'activeTenants' => Tenant::where('is_active', true)->count(),
|
||||
'users' => User::withoutGlobalScope('tenant')->where('is_platform_admin', false)->count(),
|
||||
'activeLicenses' => License::withoutGlobalScope('tenant')->where('status', LicenseStatus::Active->value)->count(),
|
||||
'installations' => DeploymentInstallation::withoutGlobalScope('tenant')->count(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$role = $user->hasRole(SystemRole::TenantAdmin->value)
|
||||
? SystemRole::TenantAdmin->value
|
||||
: SystemRole::TenantUser->value;
|
||||
|
||||
return Inertia::render('dashboard', [
|
||||
'role' => $role,
|
||||
'tenant' => $user->tenant?->only(['id', 'name', 'slug', 'is_active']),
|
||||
'stats' => [
|
||||
'users' => User::count(),
|
||||
'activeUsers' => User::where('is_active', true)->count(),
|
||||
'devices' => Device::count(),
|
||||
'canAddDevice' => $user->can('device.create'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\StoreDeviceAccessUserRequest;
|
||||
use App\Http\Requests\UpdateDeviceAccessUserRequest;
|
||||
use App\Jobs\SyncDeviceUser;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceAccessUser;
|
||||
use App\Models\DeviceUserAssignment;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class DeviceAccessUserController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
abort_unless($request->user()->can('device.user.view'), 403);
|
||||
$users = DeviceAccessUser::query()->with(['assignments' => fn ($query) => $query->with('device:id,name')])
|
||||
->when($request->integer('device_id'), fn ($query, $deviceId) => $query->whereHas('assignments', fn ($assignment) => $assignment->where('device_id', $deviceId)))
|
||||
->when($request->string('search')->isNotEmpty(), fn ($query) => $query->where(fn ($nested) => $nested->where('display_name', 'ilike', '%'.$request->string('search').'%')->orWhere('username', 'ilike', '%'.$request->string('search').'%')))
|
||||
->orderBy('display_name')->paginate(15)->withQueryString();
|
||||
|
||||
return Inertia::render('device-users/index', ['deviceUsers' => $users, 'devices' => Device::orderBy('name')->get(['id', 'name']), 'filters' => $request->only('search', 'device_id')]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
abort_unless(request()->user()->can('device.user.create'), 403);
|
||||
|
||||
return Inertia::render('device-users/form', ['deviceUser' => null, 'devices' => $this->devices()]);
|
||||
}
|
||||
|
||||
public function store(StoreDeviceAccessUserRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
$assignments = DB::transaction(function () use ($request, $data) {
|
||||
$user = DeviceAccessUser::create(['tenant_id' => $request->user()->tenant_id, 'display_name' => $data['display_name'], 'username' => $data['username'], 'password' => $data['password'], 'is_enabled' => $data['is_enabled'] ?? true, 'notes' => $data['notes'] ?? null, 'created_by' => $request->user()->id, 'updated_by' => $request->user()->id]);
|
||||
|
||||
return collect($data['device_ids'])->map(fn ($deviceId) => DeviceUserAssignment::create(['tenant_id' => $request->user()->tenant_id, 'device_access_user_id' => $user->id, 'device_id' => $deviceId, 'group_name' => $data['group_name'], 'remote_username' => $user->username, 'sync_status' => 'queued']));
|
||||
});
|
||||
$this->syncAssignments($assignments, $request->user()->tenant_id);
|
||||
|
||||
return to_route('device-users.index')->with('success', $this->syncResultMessage($assignments, 'User perangkat berhasil dibuat.'));
|
||||
}
|
||||
|
||||
public function edit(DeviceAccessUser $deviceAccessUser): Response
|
||||
{
|
||||
$this->ensureOwned($deviceAccessUser);
|
||||
abort_unless(request()->user()->can('device.user.update'), 403);
|
||||
|
||||
return Inertia::render('device-users/form', ['deviceUser' => $deviceAccessUser->load('assignments:id,device_access_user_id,device_id,group_name'), 'devices' => $this->devices()]);
|
||||
}
|
||||
|
||||
public function update(UpdateDeviceAccessUserRequest $request, DeviceAccessUser $deviceAccessUser): RedirectResponse
|
||||
{
|
||||
$this->ensureOwned($deviceAccessUser);
|
||||
$data = $request->validated();
|
||||
$assignments = DB::transaction(function () use ($request, $data, $deviceAccessUser) {
|
||||
$oldUsername = $deviceAccessUser->username;
|
||||
$attributes = ['display_name' => $data['display_name'], 'username' => $data['username'], 'is_enabled' => $data['is_enabled'] ?? false, 'notes' => $data['notes'] ?? null, 'updated_by' => $request->user()->id];
|
||||
if (filled($data['password'] ?? null)) {
|
||||
$attributes['password'] = $data['password'];
|
||||
}
|
||||
$deviceAccessUser->update($attributes);
|
||||
$deviceAccessUser->assignments()->whereNull('remote_username')->update(['remote_username' => $oldUsername]);
|
||||
$selected = collect($data['device_ids'])->map(fn ($id) => (int) $id);
|
||||
$deviceAccessUser->assignments()->whereNotIn('device_id', $selected)->update(['desired_operation' => 'delete', 'sync_status' => 'queued']);
|
||||
foreach ($selected as $deviceId) {
|
||||
$deviceAccessUser->assignments()->updateOrCreate(['device_id' => $deviceId], ['tenant_id' => $request->user()->tenant_id, 'group_name' => $data['group_name'], 'desired_operation' => 'upsert', 'sync_status' => 'queued', 'error_code' => null]);
|
||||
}
|
||||
|
||||
return $deviceAccessUser->assignments()->where('sync_status', 'queued')->get();
|
||||
});
|
||||
$this->syncAssignments($assignments, $request->user()->tenant_id);
|
||||
|
||||
return to_route('device-users.index')->with('success', $this->syncResultMessage($assignments, 'Perubahan user berhasil disimpan.'));
|
||||
}
|
||||
|
||||
public function destroy(DeviceAccessUser $deviceAccessUser): RedirectResponse
|
||||
{
|
||||
$this->ensureOwned($deviceAccessUser);
|
||||
abort_unless(request()->user()->can('device.user.delete'), 403);
|
||||
$assignments = DB::transaction(function () use ($deviceAccessUser) {
|
||||
$deviceAccessUser->assignments()->update(['desired_operation' => 'delete', 'sync_status' => 'queued']);
|
||||
$items = $deviceAccessUser->assignments()->get();
|
||||
$deviceAccessUser->delete();
|
||||
|
||||
return $items;
|
||||
});
|
||||
$this->syncAssignments($assignments, request()->user()->tenant_id);
|
||||
|
||||
return back()->with('success', $this->syncResultMessage($assignments, 'Penghapusan user perangkat diproses.'));
|
||||
}
|
||||
|
||||
private function devices()
|
||||
{
|
||||
return Device::with('vendor:id,name')->where('is_active', true)->orderBy('name')->get(['id', 'name', 'device_vendor_id', 'status', 'activation_status']);
|
||||
}
|
||||
|
||||
private function ensureOwned(DeviceAccessUser $user): void
|
||||
{
|
||||
abort_unless(! request()->user()->is_platform_admin && $user->tenant_id === request()->user()->tenant_id, 404);
|
||||
}
|
||||
|
||||
private function syncResultMessage($assignments, string $prefix): string
|
||||
{
|
||||
$remaining = DeviceUserAssignment::withoutGlobalScope('tenant')->whereIn('id', $assignments->pluck('id'))->get()->keyBy('id');
|
||||
$statuses = $assignments->map(function ($assignment) use ($remaining) {
|
||||
return $remaining->get($assignment->id)?->sync_status
|
||||
?? ($assignment->desired_operation === 'delete' ? 'synced' : 'failed');
|
||||
});
|
||||
$pending = $statuses->filter(fn ($status) => in_array($status, ['pending', 'queued'], true))->count();
|
||||
$synced = $statuses->where('synced')->count();
|
||||
$failed = $statuses->filter(fn ($status) => in_array($status, ['failed', 'unsupported'], true))->count();
|
||||
|
||||
return $prefix." {$synced} perangkat langsung tersinkron"
|
||||
.($pending ? ", {$pending} perangkat pending dan akan dicoba scheduler" : '')
|
||||
.($failed ? ", {$failed} perangkat memerlukan pemeriksaan" : '').'.';
|
||||
}
|
||||
|
||||
private function syncAssignments($assignments, int $tenantId): void
|
||||
{
|
||||
$assignments->each(function (DeviceUserAssignment $assignment) use ($tenantId): void {
|
||||
$isSshOlt = in_array($assignment->device()->with('vendor:id,slug')->first()?->vendor?->slug, ['zte', 'hsgq'], true);
|
||||
$isSshOlt
|
||||
? SyncDeviceUser::dispatch($tenantId, $assignment->id)
|
||||
: SyncDeviceUser::dispatchSync($tenantId, $assignment->id);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Jobs\SyncDeviceBaseCredential;
|
||||
use App\Models\Device;
|
||||
use App\Models\TenantDevicePolicy;
|
||||
use App\Services\Devices\MikrotikActivationService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Throwable;
|
||||
|
||||
class DeviceActivationController extends Controller
|
||||
{
|
||||
public function __invoke(Device $device, MikrotikActivationService $service): RedirectResponse
|
||||
{
|
||||
$user = request()->user();
|
||||
abort_if($user->is_platform_admin || $device->tenant_id !== $user->tenant_id, 404);
|
||||
abort_unless($user->can('device.connect'), 403);
|
||||
if ($user->hasRole(SystemRole::TenantUser->value)) {
|
||||
$policy = TenantDevicePolicy::where('tenant_id', $user->tenant_id)->first();
|
||||
abort_if(! $policy?->tenant_user_can_update_own || $device->created_by !== $user->id, 403);
|
||||
}
|
||||
|
||||
$device->update(['activation_status' => 'processing', 'activation_message' => null]);
|
||||
try {
|
||||
$queuedOlt = ($device->vendor?->slug === 'zte' && $device->hasConnection('ssh'))
|
||||
|| ($device->vendor?->slug === 'hsgq' && ($device->hasConnection('ssh') || $device->hasConnection('telnet')))
|
||||
|| ($device->vendor?->slug === 'hisfocus' && ($device->hasConnection('ssh') || $device->hasConnection('telnet')));
|
||||
if ($queuedOlt) {
|
||||
$device->update(['base_sync_status' => 'queued', 'base_sync_error_code' => null, 'base_sync_message' => 'Aktivasi OLT menunggu queue worker.']);
|
||||
SyncDeviceBaseCredential::dispatch($device->tenant_id, $device->id);
|
||||
|
||||
return back()->with('success', 'Aktivasi OLT dimasukkan ke queue. Status akan diperbarui setelah proses SSH selesai.');
|
||||
}
|
||||
|
||||
$result = $service->activate($device->load('vendor'));
|
||||
$info = $result['device_info'];
|
||||
$deleted = count($result['cleanup']['deleted']);
|
||||
$device->update([
|
||||
'hostname' => $info['identity'] ?? $device->hostname,
|
||||
'serial_number' => $info['serial-number'] ?? $device->serial_number,
|
||||
'firmware_version' => $info['current-firmware'] ?? $device->firmware_version,
|
||||
'software_version' => $info['version'] ?? $device->software_version,
|
||||
'device_facts' => $info, 'status' => 'online', 'last_seen_at' => now(),
|
||||
'activation_status' => 'active', 'activated_at' => now(),
|
||||
'activation_message' => 'Base User RADIQ aktif'.($deleted > 0 ? "; {$deleted} user lama dihapus." : '.'),
|
||||
'base_sync_status' => 'synced', 'base_sync_error_code' => null,
|
||||
'base_sync_message' => 'Base User berhasil diverifikasi saat aktivasi.', 'base_synced_at' => now(),
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Perangkat aktif dan akun management RADIQ berhasil dibuat.');
|
||||
} catch (Throwable $exception) {
|
||||
report($exception);
|
||||
$message = str($exception->getMessage())->before(':')->limit(80)->toString();
|
||||
$device->update(['activation_status' => 'failed', 'activation_message' => $message]);
|
||||
|
||||
return back()->withErrors(['activation' => 'Aktivasi gagal: '.$message]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Http\Requests\StoreDeviceRequest;
|
||||
use App\Http\Requests\UpdateDeviceRequest;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceCredential;
|
||||
use App\Models\DeviceModel;
|
||||
use App\Models\DeviceType;
|
||||
use App\Models\DeviceVendor;
|
||||
use App\Models\TenantDevicePolicy;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class DeviceController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$this->ensureTenantAccount();
|
||||
abort_unless($request->user()->can('device.view'), 403);
|
||||
$devices = Device::query()->with(['vendor:id,name', 'type:id,name', 'model:id,name', 'creator:id,name'])->withCount('userAssignments')
|
||||
->when($request->string('search')->isNotEmpty(), fn ($query) => $query->where(fn ($nested) => $nested
|
||||
->where('name', 'ilike', '%'.$request->string('search').'%')
|
||||
->orWhere('management_address', 'ilike', '%'.$request->string('search').'%')
|
||||
->orWhere('location', 'ilike', '%'.$request->string('search').'%')))
|
||||
->when($request->integer('vendor'), fn ($query, $vendor) => $query->where('device_vendor_id', $vendor))
|
||||
->when($request->string('status')->isNotEmpty(), fn ($query) => $query->where('status', $request->string('status')))
|
||||
->latest()->paginate(15)->withQueryString();
|
||||
|
||||
return Inertia::render('devices/index', ['devices' => $devices, 'vendors' => DeviceVendor::where('is_active', true)->get(['id', 'name']), 'filters' => $request->only('search', 'vendor', 'status'), 'policy' => $this->policy(), 'isTenantAdmin' => $request->user()->hasRole(SystemRole::TenantAdmin->value)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
$this->authorizeCreate();
|
||||
|
||||
return Inertia::render('devices/form', $this->catalog() + ['device' => null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StoreDeviceRequest $request): RedirectResponse
|
||||
{
|
||||
$this->authorizeCreate();
|
||||
$this->validateModelCombination($request);
|
||||
$data = $request->validated();
|
||||
$username = $data['initial_username'];
|
||||
$password = $data['initial_password'] ?? '';
|
||||
$enablePassword = $data['initial_enable_password'] ?? null;
|
||||
unset($data['initial_username'], $data['initial_password'], $data['initial_enable_password']);
|
||||
|
||||
DB::transaction(function () use ($data, $username, $password, $enablePassword, $request): void {
|
||||
$device = Device::create($data + ['tenant_id' => $request->user()->tenant_id, 'created_by' => $request->user()->id, 'updated_by' => $request->user()->id]);
|
||||
DeviceCredential::create([
|
||||
'tenant_id' => $request->user()->tenant_id, 'device_id' => $device->id,
|
||||
'name' => 'Login Awal', 'username' => $username, 'password' => $password,
|
||||
'enable_password' => $enablePassword,
|
||||
'connection_type' => $device->connection_type, 'privilege_type' => 'master',
|
||||
'is_master' => true, 'is_active' => true,
|
||||
'created_by' => $request->user()->id, 'updated_by' => $request->user()->id,
|
||||
]);
|
||||
});
|
||||
|
||||
return to_route('devices.index')->with('success', 'Perangkat berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Device $device): Response
|
||||
{
|
||||
$this->ensureOwnedDevice($device);
|
||||
abort_unless(request()->user()->can('device.view'), 403);
|
||||
|
||||
return Inertia::render('devices/show', [
|
||||
'device' => $device->load(['vendor:id,name,slug', 'type:id,name', 'model:id,name', 'creator:id,name']),
|
||||
'credentials' => request()->user()->can('device.credential.view')
|
||||
? $device->credentials()->get(['id', 'name', 'username', 'connection_type', 'privilege_type', 'is_master', 'is_active', 'last_verified_at'])
|
||||
: [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(Device $device): Response
|
||||
{
|
||||
$this->ensureOwnedDevice($device);
|
||||
$this->authorizeUpdate($device);
|
||||
|
||||
return Inertia::render('devices/form', $this->catalog() + ['device' => $device]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateDeviceRequest $request, Device $device): RedirectResponse
|
||||
{
|
||||
$this->ensureOwnedDevice($device);
|
||||
$this->authorizeUpdate($device);
|
||||
$this->validateModelCombination($request);
|
||||
$device->update($request->validated() + ['updated_by' => $request->user()->id]);
|
||||
|
||||
return to_route('devices.show', $device)->with('success', 'Perangkat berhasil diperbarui.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Device $device): RedirectResponse
|
||||
{
|
||||
$this->ensureOwnedDevice($device);
|
||||
abort_unless(request()->user()->can('device.delete'), 403);
|
||||
abort_if(request()->user()->hasRole(SystemRole::TenantUser->value) && (! $this->policy()->tenant_user_can_delete_own || $device->created_by !== request()->user()->id), 403);
|
||||
$device->delete();
|
||||
|
||||
return to_route('devices.index')->with('success', 'Perangkat berhasil dihapus.');
|
||||
}
|
||||
|
||||
private function catalog(): array
|
||||
{
|
||||
return ['vendors' => DeviceVendor::where('is_active', true)->get(['id', 'name', 'slug']), 'types' => DeviceType::where('is_active', true)->get(['id', 'name']), 'models' => DeviceModel::where('is_active', true)->get(['id', 'name', 'device_vendor_id', 'device_type_id'])];
|
||||
}
|
||||
|
||||
private function policy(): TenantDevicePolicy
|
||||
{
|
||||
return TenantDevicePolicy::firstOrCreate(['tenant_id' => request()->user()->tenant_id]);
|
||||
}
|
||||
|
||||
private function ensureTenantAccount(): void
|
||||
{
|
||||
abort_if(request()->user()->is_platform_admin || ! request()->user()->tenant_id, 403);
|
||||
}
|
||||
|
||||
private function ensureOwnedDevice(Device $device): void
|
||||
{
|
||||
$this->ensureTenantAccount();
|
||||
abort_unless($device->tenant_id === request()->user()->tenant_id, 404);
|
||||
}
|
||||
|
||||
private function authorizeCreate(): void
|
||||
{
|
||||
$this->ensureTenantAccount();
|
||||
abort_unless(request()->user()->can('device.create'), 403);
|
||||
abort_if(request()->user()->hasRole(SystemRole::TenantUser->value) && ! $this->policy()->tenant_user_can_create, 403);
|
||||
}
|
||||
|
||||
private function authorizeUpdate(Device $device): void
|
||||
{
|
||||
abort_unless(request()->user()->can('device.update'), 403);
|
||||
abort_if(request()->user()->hasRole(SystemRole::TenantUser->value) && (! $this->policy()->tenant_user_can_update_own || $device->created_by !== request()->user()->id), 403);
|
||||
}
|
||||
|
||||
private function validateModelCombination(Request $request): void
|
||||
{
|
||||
if ($request->filled('device_model_id')) {
|
||||
abort_unless(DeviceModel::whereKey($request->integer('device_model_id'))->where('device_vendor_id', $request->integer('device_vendor_id'))->where('device_type_id', $request->integer('device_type_id'))->exists(), 422, 'Model tidak sesuai dengan vendor dan tipe perangkat.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\StoreDeviceCredentialRequest;
|
||||
use App\Http\Requests\UpdateDeviceCredentialRequest;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceCredential;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class DeviceCredentialController extends Controller
|
||||
{
|
||||
public function store(StoreDeviceCredentialRequest $request, Device $device): RedirectResponse
|
||||
{
|
||||
$this->ensureOwned($device);
|
||||
$data = $request->validated();
|
||||
$data['is_master'] = $data['privilege_type'] === 'master' || ($data['is_master'] ?? false);
|
||||
abort_if(($data['is_master'] ?? false) && ! $request->user()->can('device.master_account.manage'), 403);
|
||||
DeviceCredential::create($data + ['tenant_id' => $request->user()->tenant_id, 'device_id' => $device->id, 'created_by' => $request->user()->id, 'updated_by' => $request->user()->id]);
|
||||
|
||||
return back()->with('success', 'Credential perangkat berhasil disimpan secara terenkripsi.');
|
||||
}
|
||||
|
||||
public function update(UpdateDeviceCredentialRequest $request, Device $device, DeviceCredential $credential): RedirectResponse
|
||||
{
|
||||
$this->ensureOwned($device, $credential);
|
||||
abort_if($credential->is_master && ! $request->user()->can('device.master_account.manage'), 403);
|
||||
$data = $request->validated();
|
||||
$data['is_master'] = $data['privilege_type'] === 'master' || ($data['is_master'] ?? false);
|
||||
if (blank($data['password'] ?? null)) {
|
||||
unset($data['password']);
|
||||
}
|
||||
if (blank($data['enable_password'] ?? null)) {
|
||||
unset($data['enable_password']);
|
||||
}
|
||||
$credential->update($data + ['updated_by' => $request->user()->id]);
|
||||
|
||||
return back()->with('success', 'Credential perangkat berhasil diperbarui.');
|
||||
}
|
||||
|
||||
public function destroy(Device $device, DeviceCredential $credential): RedirectResponse
|
||||
{
|
||||
$this->ensureOwned($device, $credential);
|
||||
abort_unless(request()->user()->can('device.credential.delete'), 403);
|
||||
abort_if($credential->is_master && ! request()->user()->can('device.master_account.manage'), 403);
|
||||
$credential->delete();
|
||||
|
||||
return back()->with('success', 'Credential perangkat berhasil dihapus.');
|
||||
}
|
||||
|
||||
private function ensureOwned(Device $device, ?DeviceCredential $credential = null): void
|
||||
{
|
||||
abort_if(request()->user()->is_platform_admin || $device->tenant_id !== request()->user()->tenant_id, 404);
|
||||
abort_if($credential && ($credential->tenant_id !== $device->tenant_id || $credential->device_id !== $device->id), 404);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\SyncDeviceUser;
|
||||
use App\Models\DeviceUserAssignment;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class DeviceUserSyncController extends Controller
|
||||
{
|
||||
public function __invoke(DeviceUserAssignment $assignment): RedirectResponse
|
||||
{
|
||||
abort_unless(request()->user()->can('device.user.update'), 403);
|
||||
abort_unless($assignment->tenant_id === request()->user()->tenant_id, 404);
|
||||
$assignment->update(['sync_status' => 'queued', 'error_code' => null, 'message' => null]);
|
||||
$isSshOlt = in_array($assignment->device()->with('vendor:id,slug')->first()?->vendor?->slug, ['zte', 'hsgq'], true);
|
||||
$isSshOlt
|
||||
? SyncDeviceUser::dispatch(request()->user()->tenant_id, $assignment->id)
|
||||
: SyncDeviceUser::dispatchSync(request()->user()->tenant_id, $assignment->id);
|
||||
|
||||
return back()->with('success', 'Sinkronisasi ulang langsung dijalankan. Jika perangkat offline, proses akan dicoba kembali oleh scheduler.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Models\TenantDeviceSetting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class RevealTenantBasePasswordController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
abort_unless($request->user()->hasRole(SystemRole::TenantAdmin->value), 403);
|
||||
$data = $request->validate(['current_password' => ['required', 'string']]);
|
||||
if (! Hash::check($data['current_password'], $request->user()->password)) {
|
||||
throw ValidationException::withMessages(['current_password' => 'Password login tidak sesuai.']);
|
||||
}
|
||||
|
||||
$setting = TenantDeviceSetting::where('tenant_id', $request->user()->tenant_id)->firstOrFail();
|
||||
Log::notice('Tenant Admin revealed the base device password.', [
|
||||
'tenant_id' => $request->user()->tenant_id,
|
||||
'user_id' => $request->user()->id,
|
||||
]);
|
||||
|
||||
return response()->json(['password' => $setting->base_password])
|
||||
->header('Cache-Control', 'no-store, private')
|
||||
->header('Pragma', 'no-cache');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Settings\ProfileDeleteRequest;
|
||||
use App\Http\Requests\Settings\ProfileUpdateRequest;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user's profile settings page.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
return Inertia::render('settings/profile', [
|
||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
||||
'status' => $request->session()->get('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
Inertia::flash('toast', ['type' => 'success', 'message' => __('Profile updated.')]);
|
||||
|
||||
return to_route('profile.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's profile.
|
||||
*/
|
||||
public function destroy(ProfileDeleteRequest $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Settings\PasswordUpdateRequest;
|
||||
use App\Http\Requests\Settings\TwoFactorAuthenticationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Laravel\Fortify\Features;
|
||||
|
||||
class SecurityController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user's security settings page.
|
||||
*/
|
||||
public function edit(TwoFactorAuthenticationRequest $request): Response
|
||||
{
|
||||
$props = [
|
||||
'canManageTwoFactor' => Features::canManageTwoFactorAuthentication(),
|
||||
'canManagePasskeys' => Features::canManagePasskeys(),
|
||||
'passkeys' => Features::canManagePasskeys()
|
||||
? $request->user()
|
||||
->passkeys()
|
||||
->select(['id', 'name', 'credential', 'created_at', 'last_used_at'])
|
||||
->latest()
|
||||
->get()
|
||||
->map(fn ($passkey) => [
|
||||
'id' => $passkey->id,
|
||||
'name' => $passkey->name,
|
||||
'authenticator' => $passkey->authenticator,
|
||||
'created_at_diff' => $passkey->created_at->diffForHumans(),
|
||||
'last_used_at_diff' => $passkey->last_used_at?->diffForHumans(),
|
||||
])
|
||||
->values()
|
||||
->all()
|
||||
: [],
|
||||
'passwordRules' => Password::defaults()->toPasswordRulesString(),
|
||||
];
|
||||
|
||||
if (Features::canManageTwoFactorAuthentication()) {
|
||||
$request->ensureStateIsValid();
|
||||
|
||||
$props['twoFactorEnabled'] = $request->user()->hasEnabledTwoFactorAuthentication();
|
||||
$props['requiresConfirmation'] = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm');
|
||||
}
|
||||
|
||||
return Inertia::render('settings/security', $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(PasswordUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->update([
|
||||
'password' => $request->password,
|
||||
]);
|
||||
|
||||
Inertia::flash('toast', ['type' => 'success', 'message' => __('Password updated.')]);
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Models\TenantDevicePolicy;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantDevicePolicyController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->hasRole(SystemRole::TenantAdmin->value), 403);
|
||||
$data = $request->validate([
|
||||
'tenant_user_can_create' => ['required', 'boolean'],
|
||||
'tenant_user_can_update_own' => ['required', 'boolean'],
|
||||
'tenant_user_can_delete_own' => ['required', 'boolean'],
|
||||
]);
|
||||
TenantDevicePolicy::updateOrCreate(['tenant_id' => $request->user()->tenant_id], $data);
|
||||
|
||||
return back()->with('success', 'Aturan perangkat Tenant User berhasil diperbarui.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Jobs\SyncDeviceBaseCredential;
|
||||
use App\Models\Device;
|
||||
use App\Models\TenantDeviceSetting;
|
||||
use App\Models\TenantEncryptionKey;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class TenantDeviceSettingController extends Controller
|
||||
{
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
abort_unless($request->user()->hasRole(SystemRole::TenantAdmin->value), 403);
|
||||
$setting = TenantDeviceSetting::where('tenant_id', $request->user()->tenant_id)->first();
|
||||
|
||||
$key = TenantEncryptionKey::withoutGlobalScope('tenant')->where('tenant_id', $request->user()->tenant_id)->where('is_active', true)->first();
|
||||
|
||||
return Inertia::render('devices/settings', [
|
||||
'setting' => $setting?->only(['base_username', 'use_tls', 'verify_tls', 'connection_timeout']),
|
||||
'hasPassword' => (bool) $setting,
|
||||
'encryptionKey' => $key?->only(['version', 'source', 'created_at']),
|
||||
'devices' => Device::with('vendor:id,name')->orderBy('name')->get(['id', 'name', 'device_vendor_id', 'base_sync_status', 'base_sync_error_code', 'base_sync_message', 'base_synced_at']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->hasRole(SystemRole::TenantAdmin->value), 403);
|
||||
$setting = TenantDeviceSetting::where('tenant_id', $request->user()->tenant_id)->first();
|
||||
$data = $request->validate([
|
||||
'base_username' => ['required', 'regex:/^[A-Za-z0-9][A-Za-z0-9_.@#-]*[A-Za-z0-9]$/', 'max:64'],
|
||||
'base_password' => [$setting ? 'nullable' : 'required', 'confirmed', Password::min(12)->mixedCase()->numbers()->symbols()],
|
||||
'use_tls' => ['required', 'boolean'], 'verify_tls' => ['required', 'boolean'],
|
||||
'connection_timeout' => ['required', 'integer', 'between:3,30'],
|
||||
]);
|
||||
if (blank($data['base_password'] ?? null)) {
|
||||
unset($data['base_password']);
|
||||
}
|
||||
$oldUsername = $setting?->base_username;
|
||||
$credentialChanged = ! $setting || $oldUsername !== $data['base_username'] || array_key_exists('base_password', $data);
|
||||
TenantDeviceSetting::updateOrCreate(['tenant_id' => $request->user()->tenant_id], $data);
|
||||
if ($credentialChanged) {
|
||||
Device::where('tenant_id', $request->user()->tenant_id)->where('is_active', true)->get(['id', 'tenant_id'])->each(function (Device $device) use ($oldUsername): void {
|
||||
$device->update(['base_sync_status' => 'queued', 'base_sync_error_code' => null, 'base_sync_message' => 'Perubahan Base User menunggu sinkronisasi.']);
|
||||
SyncDeviceBaseCredential::dispatch($device->tenant_id, $device->id, $oldUsername);
|
||||
});
|
||||
}
|
||||
|
||||
return back()->with('success', $credentialChanged ? 'Base User tersimpan terenkripsi dan rotasi seluruh perangkat tenant dimasukkan ke queue.' : 'Pengaturan koneksi perangkat berhasil disimpan.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Services\Encryption\TenantEnvelopeEncryption;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class TenantEncryptionController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, TenantEnvelopeEncryption $encryption): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->hasRole(SystemRole::TenantAdmin->value), 403);
|
||||
$data = $request->validate([
|
||||
'mode' => ['required', 'in:generated,manual'],
|
||||
'manual_key' => ['nullable', 'required_if:mode,manual', 'string', 'min:32', 'max:4096', 'confirmed'],
|
||||
'current_password' => ['required', 'string'],
|
||||
]);
|
||||
abort_unless(Hash::check($data['current_password'], $request->user()->password), 422, 'Password akun Tenant Admin tidak valid.');
|
||||
$key = $encryption->rotate($request->user()->tenant_id, $data['mode'] === 'manual' ? $data['manual_key'] : null, $request->user()->id);
|
||||
|
||||
return back()->with('success', "Encryption key tenant berhasil dirotasi ke versi {$key->version}. Semua secret database telah dienkripsi ulang.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureActiveUser
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->user() && ! $request->user()->is_active) {
|
||||
Auth::guard('web')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login')->withErrors(['email' => 'Akun Anda sedang dinonaktifkan.']);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class HandleAppearance
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
View::share('appearance', $request->cookie('appearance') ?? 'system');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Middleware;
|
||||
|
||||
class HandleInertiaRequests extends Middleware
|
||||
{
|
||||
/**
|
||||
* The root template that's loaded on the first page visit.
|
||||
*
|
||||
* @see https://inertiajs.com/server-side-setup#root-template
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rootView = 'app';
|
||||
|
||||
/**
|
||||
* Determines the current asset version.
|
||||
*
|
||||
* @see https://inertiajs.com/asset-versioning
|
||||
*/
|
||||
public function version(Request $request): ?string
|
||||
{
|
||||
return parent::version($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the props that are shared by default.
|
||||
*
|
||||
* @see https://inertiajs.com/shared-data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function share(Request $request): array
|
||||
{
|
||||
return [
|
||||
...parent::share($request),
|
||||
'name' => config('app.name'),
|
||||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
'permissions' => fn () => $request->user()?->getAllPermissions()->pluck('name')->values() ?? [],
|
||||
],
|
||||
'flash' => [
|
||||
'success' => fn () => $request->session()->get('success'),
|
||||
],
|
||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use App\Support\TenantContext;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SelectAdministrationTenant
|
||||
{
|
||||
public function __construct(private readonly TenantContext $context) {}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$tenant = $request->route('tenant');
|
||||
abort_unless($tenant instanceof Tenant, 404);
|
||||
|
||||
$user = $request->user();
|
||||
abort_unless($user?->is_platform_admin || $user?->tenant_id === $tenant->id, 403);
|
||||
|
||||
$previousTenantId = $this->context->id();
|
||||
$this->context->set($tenant->id);
|
||||
setPermissionsTeamId($tenant->id);
|
||||
|
||||
try {
|
||||
return $next($request);
|
||||
} finally {
|
||||
$this->context->set($previousTenantId);
|
||||
setPermissionsTeamId($previousTenantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Support\TenantContext;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SetTenantContext
|
||||
{
|
||||
public function __construct(private readonly TenantContext $context) {}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
$requestedTenantId = $request->header('X-Tenant-ID');
|
||||
|
||||
if ($user?->is_platform_admin && $requestedTenantId !== null) {
|
||||
abort_unless($user->can('tenant.access-any'), 403);
|
||||
$this->context->set((int) $requestedTenantId);
|
||||
} else {
|
||||
$this->context->set($user?->tenant_id);
|
||||
}
|
||||
|
||||
setPermissionsTeamId($this->context->id());
|
||||
|
||||
try {
|
||||
return $next($request);
|
||||
} finally {
|
||||
$this->context->clear();
|
||||
setPermissionsTeamId(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Administration;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class ResetUserPasswordRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('user.update') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'password' => ['required', 'confirmed', Password::min(12)->mixedCase()->numbers()->symbols()],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Administration;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class StoreTenantRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('tenant.create') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'slug' => ['required', 'alpha_dash', 'max:100', 'unique:tenants,slug'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
'owner_name' => ['required', 'string', 'max:255'],
|
||||
'owner_email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
||||
'owner_password' => ['required', 'confirmed', Password::min(12)->mixedCase()->numbers()->symbols()],
|
||||
'deployment_type' => ['sometimes', 'in:managed_cloud,self_hosted'],
|
||||
'deployment_domain' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Administration;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class StoreUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('user.create') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
||||
'password' => ['required', 'confirmed', Password::min(12)->mixedCase()->numbers()->symbols()],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Administration;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateTenantRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('tenant.update') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'slug' => ['required', 'alpha_dash', 'max:100', 'unique:tenants,slug,'.$this->route('tenant')->id],
|
||||
'is_active' => ['required', 'boolean'],
|
||||
'deployment_type' => ['sometimes', 'in:managed_cloud,self_hosted'],
|
||||
'deployment_domain' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Administration;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('user.update') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($this->route('user')->id)],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Concerns\PasswordValidationRules;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PasswordUpdateRequest extends FormRequest
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'current_password' => $this->currentPasswordRules(),
|
||||
'password' => $this->passwordRules(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Concerns\PasswordValidationRules;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProfileDeleteRequest extends FormRequest
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'password' => $this->currentPasswordRules(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Concerns\ProfileValidationRules;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
use ProfileValidationRules;
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return $this->profileRules($this->user()->id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Laravel\Fortify\InteractsWithTwoFactorState;
|
||||
|
||||
class TwoFactorAuthenticationRequest extends FormRequest
|
||||
{
|
||||
use InteractsWithTwoFactorState;
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Device;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class StoreDeviceAccessUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('device.user.create') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return $this->rulesForUser();
|
||||
}
|
||||
|
||||
protected function rulesForUser(?int $ignoreId = null): array
|
||||
{
|
||||
$tenantId = $this->user()->tenant_id;
|
||||
|
||||
return [
|
||||
'display_name' => ['required', 'string', 'max:255'],
|
||||
'username' => ['required', 'regex:/^[A-Za-z0-9][A-Za-z0-9_.@#-]*[A-Za-z0-9]$/', 'max:64', Rule::unique('device_access_users')->where('tenant_id', $tenantId)->ignore($ignoreId)],
|
||||
'password' => ['required', 'confirmed', Password::min(12)->mixedCase()->numbers()->symbols()],
|
||||
'group_name' => ['required', 'in:RADIQ-READ,RADIQ-WRITE,RADIQ-NOC'],
|
||||
'device_ids' => ['required', 'array', 'min:1'],
|
||||
'device_ids.*' => ['integer', Rule::exists('devices', 'id')->where('tenant_id', $tenantId)],
|
||||
'is_enabled' => ['sometimes', 'boolean'], 'notes' => ['nullable', 'string', 'max:5000'],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $validator): void {
|
||||
$hasZte = Device::whereIn('id', $this->input('device_ids', []))->whereHas('vendor', fn ($query) => $query->where('slug', 'zte'))->exists();
|
||||
if ($hasZte && ! preg_match('/^[A-Za-z0-9_]{1,16}$/', (string) $this->input('username'))) {
|
||||
$validator->errors()->add('username', 'Untuk ZTE C300/C320, username harus 1-16 karakter alfanumerik atau underscore.');
|
||||
}
|
||||
$password = (string) $this->input('password');
|
||||
if ($hasZte && $password !== '' && (strlen($password) > 32 || preg_match('/\s/', $password))) {
|
||||
$validator->errors()->add('password', 'Untuk ZTE C300/C320, password maksimal 32 karakter dan tidak boleh mengandung spasi.');
|
||||
}
|
||||
|
||||
$hasHsgq = Device::whereIn('id', $this->input('device_ids', []))->whereHas('vendor', fn ($query) => $query->where('slug', 'hsgq'))->exists();
|
||||
if ($hasHsgq && ! preg_match('/^[A-Za-z0-9_]{4,16}$/', (string) $this->input('username'))) {
|
||||
$validator->errors()->add('username', 'Untuk HSGQ, username harus 4-16 karakter alfanumerik atau underscore.');
|
||||
}
|
||||
if ($hasHsgq && $password !== '' && (strlen($password) > 64 || preg_match('/\s/', $password))) {
|
||||
$validator->errors()->add('password', 'Untuk HSGQ, password maksimal 64 karakter dan tidak boleh mengandung spasi.');
|
||||
}
|
||||
$hasHisfocus = Device::whereIn('id', $this->input('device_ids', []))->whereHas('vendor', fn ($query) => $query->where('slug', 'hisfocus'))->exists();
|
||||
if ($hasHisfocus && ! preg_match('/^[A-Za-z][A-Za-z0-9_]{3,15}$/', (string) $this->input('username'))) {
|
||||
$validator->errors()->add('username', 'Untuk Hisfocus, username harus 4-16 karakter, diawali huruf, dan hanya alfanumerik atau underscore.');
|
||||
}
|
||||
if ($hasHisfocus && $password !== '' && (strlen($password) > 64 || preg_match('/\s/', $password))) {
|
||||
$validator->errors()->add('password', 'Untuk Hisfocus, password maksimal 64 karakter dan tidak boleh mengandung spasi.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreDeviceCredentialRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('device.credential.create') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return self::deviceCredentialRules();
|
||||
}
|
||||
|
||||
public static function deviceCredentialRules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'], 'username' => ['required', 'string', 'max:255'],
|
||||
'password' => ['required', 'string', 'max:4096'],
|
||||
'enable_password' => ['nullable', 'string', 'max:4096'],
|
||||
'connection_type' => ['required', 'in:routeros_api,ssh,telnet,snmp,vendor_api'],
|
||||
'privilege_type' => ['required', 'in:master,noc,technician,monitoring,custom'],
|
||||
'is_master' => ['sometimes', 'boolean'], 'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\DeviceVendor;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class StoreDeviceRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('device.create') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return self::deviceRules() + [
|
||||
'initial_username' => ['required', 'string', 'max:255'],
|
||||
'initial_password' => ['nullable', 'string', 'max:1000'],
|
||||
'initial_enable_password' => ['nullable', 'string', 'max:1000'],
|
||||
'remove_legacy_users_on_activation' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function deviceRules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'device_vendor_id' => ['required', 'exists:device_vendors,id'], 'device_type_id' => ['required', 'exists:device_types,id'],
|
||||
'device_model_id' => ['nullable', 'exists:device_models,id'],
|
||||
'management_address' => ['required', 'string', 'max:255'],
|
||||
'connection_type' => ['required', 'in:routeros_api,ssh,telnet,web_http,snmp,vendor_api'],
|
||||
'management_port' => ['required', 'integer', 'between:1,65535'],
|
||||
'connection_ports' => ['required', 'array'],
|
||||
'connection_ports.routeros_api' => ['nullable', 'integer', 'between:1,65535'],
|
||||
'connection_ports.ssh' => ['nullable', 'integer', 'between:1,65535'],
|
||||
'connection_ports.telnet' => ['nullable', 'integer', 'between:1,65535'],
|
||||
'connection_ports.web_http' => ['nullable', 'integer', 'between:1,65535'],
|
||||
'connection_ports.snmp' => ['nullable', 'integer', 'between:1,65535'],
|
||||
'connection_ports.vendor_api' => ['nullable', 'integer', 'between:1,65535'],
|
||||
'location' => ['nullable', 'string', 'max:255'],
|
||||
'notes' => ['nullable', 'string', 'max:5000'], 'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$ports = collect($this->input('connection_ports', []))
|
||||
->map(fn ($port) => $port === '' || $port === null ? null : (int) $port)
|
||||
->filter()
|
||||
->all();
|
||||
$slug = DeviceVendor::find($this->input('device_vendor_id'))?->slug;
|
||||
$order = match ($slug) {
|
||||
'mikrotik' => ['routeros_api'],
|
||||
'zte' => ['ssh'],
|
||||
'hsgq' => ['ssh', 'telnet'],
|
||||
'hisfocus' => ['telnet', 'web_http'],
|
||||
default => ['ssh', 'telnet', 'routeros_api', 'web_http', 'snmp', 'vendor_api'],
|
||||
};
|
||||
$primary = collect($order)->first(fn ($protocol) => isset($ports[$protocol]));
|
||||
$this->merge([
|
||||
'connection_ports' => $ports,
|
||||
'connection_type' => $primary ?? 'vendor_api',
|
||||
'management_port' => $primary ? $ports[$primary] : 1,
|
||||
]);
|
||||
}
|
||||
|
||||
public function after(): array
|
||||
{
|
||||
return [function (Validator $validator): void {
|
||||
$slug = DeviceVendor::find($this->input('device_vendor_id'))?->slug;
|
||||
$ports = $this->input('connection_ports', []);
|
||||
$valid = match ($slug) {
|
||||
'mikrotik' => isset($ports['routeros_api']),
|
||||
'zte' => isset($ports['ssh']),
|
||||
'hsgq' => isset($ports['ssh']) || isset($ports['telnet']),
|
||||
'hisfocus' => isset($ports['telnet']) || isset($ports['web_http']),
|
||||
default => count($ports) > 0,
|
||||
};
|
||||
if (! $valid) {
|
||||
$validator->errors()->add('connection_ports', 'Isi minimal satu port protokol yang didukung vendor.');
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreTenantRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('tenant.create') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'slug' => ['required', 'alpha_dash', 'max:100', 'unique:tenants,slug'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\DeviceAccessUser;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class UpdateDeviceAccessUserRequest extends StoreDeviceAccessUserRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('device.user.update') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
/** @var DeviceAccessUser $deviceAccessUser */
|
||||
$deviceAccessUser = $this->route('deviceAccessUser');
|
||||
$rules = $this->rulesForUser($deviceAccessUser->id);
|
||||
$rules['password'] = ['nullable', 'confirmed', Password::min(12)->mixedCase()->numbers()->symbols()];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateDeviceCredentialRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('device.credential.update') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = StoreDeviceCredentialRequest::deviceCredentialRules();
|
||||
$rules['password'] = ['nullable', 'string', 'max:4096'];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateDeviceRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('device.update') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return StoreDeviceRequest::deviceRules();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateTenantRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('tenant.update') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'required', 'string', 'max:255'],
|
||||
'slug' => ['sometimes', 'required', 'alpha_dash', 'max:100', 'unique:tenants,slug,'.$this->route('tenant')->id],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TenantResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $this->uuid,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
'is_active' => $this->is_active,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user