set($this->tenantId); setPermissionsTeamId($this->tenantId); try { $device = Device::with(['vendor', 'model', 'credentials'])->find($this->deviceId); $setting = TenantDeviceSetting::where('tenant_id', $this->tenantId)->first(); if (! $device || ! $setting) { return; } $device->update(['base_sync_status' => 'processing', 'base_sync_attempted_at' => now(), 'base_sync_error_code' => null]); if ($device->vendor?->slug === 'zte' && $device->hasConnection('ssh') && preg_match('/C3(?:00|20)/i', $device->model?->name ?? '')) { $this->syncZte($device, $setting); return; } if ($device->vendor?->slug === 'hsgq' && ($device->hasConnection('ssh') || $device->hasConnection('telnet'))) { $this->syncHsgq($device, $setting); return; } if ($device->vendor?->slug === 'hisfocus' && ($device->hasConnection('ssh') || $device->hasConnection('telnet'))) { $this->syncHisfocus($device, $setting); return; } if ($device->vendor?->slug !== 'mikrotik' || ! $device->hasConnection('routeros_api')) { $device->update(['base_sync_status' => 'unsupported', 'base_sync_error_code' => 'DRIVER_NOT_AVAILABLE', 'base_sync_message' => "Driver rotasi Base User untuk {$device->vendor?->name} / {$device->connection_type} belum tersedia.", 'activation_status' => 'failed', 'activation_message' => 'DRIVER_NOT_AVAILABLE']); return; } $credential = $device->credentials->where('is_active', true)->sortByDesc(fn ($item) => $item->name === 'Base User RADIQ' ? 2 : (int) $item->is_master)->first(); if (! $credential) { $device->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'CREDENTIAL_REQUIRED', 'base_sync_message' => 'Menunggu credential aktif untuk masuk ke perangkat.']); return; } $driver = new MikrotikDriver($this->client($device, $credential->username, $credential->password, $setting)); $driver->provisionBaseAccess($setting->base_username, $setting->base_password); $newDriver = new MikrotikDriver($this->client($device, $setting->base_username, $setting->base_password, $setting)); $newDriver->testConnection(); $previousUsername = $this->oldUsername ?? ($credential->name === 'Base User RADIQ' ? $credential->username : null); if ($previousUsername && $previousUsername !== $setting->base_username) { $newDriver->deleteUser($previousUsername); } $device->credentials()->update(['is_master' => false]); $device->credentials()->updateOrCreate(['name' => 'Base User RADIQ'], [ 'tenant_id' => $device->tenant_id, 'username' => $setting->base_username, 'password' => $setting->base_password, 'connection_type' => $device->connection_type, 'privilege_type' => 'master', 'is_master' => true, 'is_active' => true, 'last_verified_at' => now(), ]); $device->update(['base_sync_status' => 'synced', 'base_sync_error_code' => null, 'base_sync_message' => 'Base User berhasil diperbarui pada perangkat.', 'base_synced_at' => now(), 'status' => 'online', 'last_seen_at' => now()]); } catch (Throwable $exception) { report($exception); $code = str($exception->getMessage())->before(':')->limit(64)->toString() ?: 'UNKNOWN_ERROR'; $pending = in_array($code, ['DEVICE_UNREACHABLE', 'CONNECTION_TIMEOUT', 'CONNECTION_FAILED'], true); Device::whereKey($this->deviceId)->update([ 'base_sync_status' => $pending ? 'pending' : 'failed', 'base_sync_error_code' => $code, 'base_sync_message' => $pending ? 'Perangkat belum terhubung; scheduler akan mencoba kembali.' : 'Rotasi Base User gagal dan perlu diperiksa.', 'status' => $pending ? 'offline' : 'unknown', 'activation_status' => $pending ? 'pending' : 'failed', 'activation_message' => $code, ]); } finally { $context->clear(); setPermissionsTeamId(null); } } private function client(Device $device, string $username, string $password, TenantDeviceSetting $setting): RouterOsApiClient { return new RouterOsApiClient($device->management_address, $device->portFor('routeros_api'), $username, $password, $setting->connection_timeout, $setting->use_tls, $setting->verify_tls); } private function syncZte(Device $device, TenantDeviceSetting $setting): void { $credential = $device->credentials->where('is_active', true)->sortByDesc(fn ($item) => $item->name === 'Base User RADIQ' ? 2 : (int) $item->is_master)->first(); if (! $credential) { $device->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'CREDENTIAL_REQUIRED', 'base_sync_message' => 'Menunggu credential login dan enable ZTE.', 'activation_status' => 'failed', 'activation_message' => 'CREDENTIAL_REQUIRED']); return; } $driver = new ZteC3xxDriver(new ZteSshClient($device->management_address, $device->portFor('ssh'), $setting->connection_timeout)); $driver->provisionBaseAccess($credential->username, $credential->password, $credential->enable_password, $setting->base_username, $setting->base_password); $driver->testLogin($setting->base_username, $setting->base_password, $credential->enable_password); $previousUsername = $this->oldUsername ?? ($credential->name === 'Base User RADIQ' ? $credential->username : null); if ($previousUsername && $previousUsername !== $setting->base_username) { $driver->deleteUser($setting->base_username, $setting->base_password, $credential->enable_password, $previousUsername); } $device->credentials()->update(['is_master' => false]); $device->credentials()->updateOrCreate(['name' => 'Base User RADIQ'], [ 'tenant_id' => $device->tenant_id, 'username' => $setting->base_username, 'password' => $setting->base_password, 'enable_password' => $credential->enable_password, 'connection_type' => 'ssh', 'privilege_type' => 'master', 'is_master' => true, 'is_active' => true, 'last_verified_at' => now(), ]); $device->update(['base_sync_status' => 'synced', 'base_sync_error_code' => null, 'base_sync_message' => 'Base User privilege 15 berhasil diperbarui pada ZTE.', 'base_synced_at' => now(), 'status' => 'online', 'last_seen_at' => now(), 'activation_status' => 'active', 'activation_message' => 'Base User ZTE privilege 15 berhasil dibuat dan diverifikasi.', 'activated_at' => now()]); } public function failed(?Throwable $exception): void { Device::withoutGlobalScope('tenant')->whereKey($this->deviceId)->update([ 'base_sync_status' => 'failed', 'base_sync_error_code' => 'JOB_TIMEOUT', 'base_sync_message' => 'Proses koneksi perangkat melewati batas waktu queue.', 'activation_status' => 'failed', 'activation_message' => 'JOB_TIMEOUT', ]); } private function syncHsgq(Device $device, TenantDeviceSetting $setting): void { $credential = $device->credentials->where('is_active', true)->sortByDesc(fn ($item) => $item->name === 'HSGQ Root' ? 2 : (int) $item->is_master)->first(); if (! $credential || $credential->username !== 'root') { $device->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'ROOT_CREDENTIAL_REQUIRED', 'base_sync_message' => 'HSGQ membutuhkan credential root aktif.', 'activation_status' => 'failed', 'activation_message' => 'ROOT_CREDENTIAL_REQUIRED']); return; } $webClient = new HsgqWebClient($device->management_address, $setting->connection_timeout); $hsgqPrimary = $device->hasConnection('ssh') ? new HsgqSshClient($device->management_address, $device->portFor('ssh'), $setting->connection_timeout) : new HsgqTelnetClient($device->management_address, $device->portFor('telnet'), $setting->connection_timeout); $driver = new HsgqOltDriver( $hsgqPrimary, $device->hasConnection('ssh') && $device->hasConnection('telnet') ? new HsgqTelnetClient($device->management_address, $device->portFor('telnet'), $setting->connection_timeout) : null, $webClient, ); $rootPassword = $credential->password; if ($device->remove_legacy_users_on_activation && $rootPassword !== $setting->base_password) { $driver->rotateRootPassword($rootPassword, $setting->base_password, $credential->enable_password); $rootPassword = $setting->base_password; } else { $driver->testLogin('root', $rootPassword, $credential->enable_password); } $device->credentials()->update(['is_master' => false]); $device->credentials()->updateOrCreate(['name' => 'HSGQ Root'], [ 'tenant_id' => $device->tenant_id, 'username' => 'root', 'password' => $rootPassword, 'enable_password' => $credential->enable_password, 'connection_type' => 'ssh', 'privilege_type' => 'master', 'is_master' => true, 'is_active' => true, 'last_verified_at' => now(), ]); $message = $device->remove_legacy_users_on_activation ? 'Password root HSGQ telah disinkronkan dengan Base Password tenant.' : 'Credential root awal dipertahankan sesuai opsi perangkat.'; $webClient->login('root', $rootPassword); $facts = $webClient->boardInfo(); $device->update([ 'serial_number' => $facts['sn'] ?? $device->serial_number, 'firmware_version' => $facts['fw_ver'] ?? $device->firmware_version, 'software_version' => $facts['sys_ver'] ?? $device->software_version, 'device_facts' => array_merge($device->device_facts ?? [], $facts, ['management_transports' => ['ssh', 'telnet', 'webgui']]), 'base_sync_status' => 'synced', 'base_sync_error_code' => null, 'base_sync_message' => $message, 'base_synced_at' => now(), 'status' => 'online', 'last_seen_at' => now(), 'activation_status' => 'active', 'activation_message' => $message, 'activated_at' => now(), ]); } private function syncHisfocus(Device $device, TenantDeviceSetting $setting): void { $credential = $device->credentials->where('is_active', true)->sortByDesc('is_master')->first(); if (! $credential) { $device->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'CREDENTIAL_REQUIRED', 'base_sync_message' => 'Menunggu credential awal Hisfocus.', 'activation_status' => 'failed', 'activation_message' => 'CREDENTIAL_REQUIRED']); return; } $client = $device->hasConnection('telnet') ? new HsgqTelnetClient($device->management_address, $device->portFor('telnet'), $setting->connection_timeout) : new HisfocusSshClient($device->management_address, $device->portFor('ssh'), $setting->connection_timeout); $driver = new HisfocusOltDriver($client); $facts = $driver->probe($credential->username, $credential->password, $credential->enable_password); $driver->provisionBaseAccess($credential->username, $credential->password, $credential->enable_password, $setting->base_username, $setting->base_password); $webPort = $device->portFor('web_http'); if ($webPort) { $web = new HisfocusWebClient($device->management_address, $webPort, $setting->connection_timeout); $webUsers = $web->users($credential->username, $credential->password); if (! isset($webUsers[$setting->base_username])) { $web->addUser($credential->username, $credential->password, $setting->base_username, $setting->base_password, 'Administrator'); } elseif (! $web->canLogin($setting->base_username, $setting->base_password)) { $web->deleteUser($credential->username, $credential->password, $setting->base_username); $web->addUser($credential->username, $credential->password, $setting->base_username, $setting->base_password, 'Administrator'); } if (! $web->canLogin($setting->base_username, $setting->base_password)) { throw new \RuntimeException('HISFOCUS_WEB_LOGIN_FAILED: Base User belum dapat login ke WebGUI.'); } } if ($device->remove_legacy_users_on_activation && $credential->username !== $setting->base_username) { $driver->deleteUser($setting->base_username, $setting->base_password, $credential->enable_password, $credential->username); } $device->credentials()->update(['is_master' => false]); $device->credentials()->updateOrCreate(['name' => 'Base User RADIQ'], [ 'tenant_id' => $device->tenant_id, 'username' => $setting->base_username, 'password' => $setting->base_password, 'enable_password' => $credential->enable_password ?: $credential->password, 'connection_type' => $device->connection_type, 'privilege_type' => 'master', 'is_master' => true, 'is_active' => true, 'last_verified_at' => now(), ]); $message = $device->remove_legacy_users_on_activation ? 'Base User Hisfocus dibuat dan user login awal dihapus.' : 'Base User Hisfocus dibuat; user login awal dipertahankan.'; $device->update([ 'serial_number' => $facts['serial_number'] ?? $device->serial_number, 'firmware_version' => $facts['firmware_version'] ?? $device->firmware_version, 'device_facts' => array_merge($device->device_facts ?? [], $facts, ['user_management' => 'multi_user', 'roles' => ['administrator', 'operator', 'guest'], 'web_management_port' => $webPort, 'user_stores' => $webPort ? ['cli', 'webgui'] : ['cli']]), 'base_sync_status' => 'synced', 'base_sync_error_code' => null, 'base_sync_message' => $message, 'base_synced_at' => now(), 'status' => 'online', 'last_seen_at' => now(), 'activation_status' => 'active', 'activation_message' => $message, 'activated_at' => now(), ]); } }