@@ -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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user