> */ public function getRights($interface = 'central'): array { return [ READ => [ 'short' => __('Read'), 'long' => __('Read'), ], UPDATE => [ 'short' => __('Update'), 'long' => __('Update'), ], CREATE => [ 'short' => __('Create'), 'long' => __('Create'), ], DELETE => [ 'short' => __('Delete'), 'long' => __('Delete'), ], PURGE => [ 'short' => __('Purge'), 'long' => __('Purge'), ], ]; } /** * Check view right. * * @return bool */ public static function canView(): bool { return Profile::canCurrentUser(READ); } /** * Check create right. * * @return bool */ public static function canCreate(): bool { return Profile::canCurrentUser(CREATE); } /** * Check update right. * * @return bool */ public static function canUpdate(): bool { return Profile::canCurrentUser(UPDATE); } /** * Check delete right. * * @return bool */ public static function canDelete(): bool { return Profile::canCurrentUser(DELETE); } /** * Check purge right. * * @return bool */ public static function canPurge(): bool { return Profile::canCurrentUser(PURGE); } /** * Get menu name. * * @return string */ public static function getMenuName(): string { return self::getTypeName(Session::getPluralNumber()); } /** * Get menu content. * * @return array */ public static function getMenuContent(): array { $menu = []; if (self::canView()) { $menu['title'] = self::getMenuName(); $menu['page'] = self::getSearchURL(false); $menu['icon'] = 'ti ti-server'; $menu['links']['search'] = self::getSearchURL(false); if (self::canCreate()) { $menu['links']['add'] = self::getFormURL(false); } } return $menu; } /** * Define tabs. * * @param array $options Options * * @return array */ public function defineTabs($options = []): array { $ong = []; $this->addDefaultFormTab($ong); $this->addStandardTab(ServerAsset::class, $ong, $options); $this->addStandardTab('Log', $ong, $options); return $ong; } /** * Search options. * * @return array> */ public function rawSearchOptions(): array { $tab = []; $tab[] = [ 'id' => 'common', 'name' => __('Characteristics'), ]; $tab[] = [ 'id' => 1, 'table' => self::getTable(), 'field' => 'name', 'name' => __('Name'), 'datatype' => 'itemlink', 'massiveaction' => false, ]; $tab[] = [ 'id' => 2, 'table' => self::getTable(), 'field' => 'ip_address', 'name' => __('IP address', 'urbackup'), 'datatype' => 'string', ]; $tab[] = [ 'id' => 3, 'table' => self::getTable(), 'field' => 'port', 'name' => __('Network port', 'urbackup'), 'datatype' => 'integer', ]; $tab[] = [ 'id' => 4, 'table' => self::getTable(), 'field' => 'protocol', 'name' => __('Protocol', 'urbackup'), 'datatype' => 'string', ]; $tab[] = [ 'id' => 5, 'table' => self::getTable(), 'field' => 'server_version', 'name' => __('UrBackup server version', 'urbackup'), 'datatype' => 'string', ]; $tab[] = [ 'id' => 6, 'table' => Entity::getTable(), 'field' => 'completename', 'name' => Entity::getTypeName(1), 'datatype' => 'dropdown', ]; $tab[] = [ 'id' => 7, 'table' => Location::getTable(), 'field' => 'completename', 'name' => Location::getTypeName(1), 'datatype' => 'dropdown', ]; $tab[] = [ 'id' => 8, 'table' => self::getTable(), 'field' => 'is_active', 'name' => __('Active'), 'datatype' => 'bool', ]; $tab[] = [ 'id' => 9, 'table' => self::getTable(), 'field' => 'last_api_status', 'name' => __('Last API status', 'urbackup'), 'datatype' => 'bool', ]; $tab[] = [ 'id' => 10, 'table' => self::getTable(), 'field' => 'last_api_check', 'name' => __('Last API check', 'urbackup'), 'datatype' => 'datetime', ]; $tab[] = [ 'id' => 11, 'table' => self::getTable(), 'field' => 'date_creation', 'name' => __('Creation date'), 'datatype' => 'datetime', ]; $tab[] = [ 'id' => 12, 'table' => self::getTable(), 'field' => 'date_mod', 'name' => __('Last update'), 'datatype' => 'datetime', ]; $tab[] = [ 'id' => 13, 'table' => self::getTable(), 'field' => 'id', 'name' => __('View', 'urbackup'), 'massiveaction' => false, 'datatype' => 'raw', 'searchtype' => 'view', ]; return $tab; } /** * Show server form. * * @param int $ID ID * @param array $options Options * * @return bool */ public function showForm($ID, array $options = []): bool { if ($ID > 0) { $this->check($ID, READ); } else { $this->check(-1, CREATE); $this->getEmpty(); } $this->initForm($ID, $options); $this->showFormHeader($options); echo ""; echo "" . htmlspecialchars(__('Name')) . ""; echo ""; echo Html::input('name', [ 'value' => $this->fields['name'] ?? '', 'size' => 40, ]); echo ""; echo "" . htmlspecialchars(__('Active')) . ""; echo ""; Dropdown::showYesNo('is_active', (int) ($this->fields['is_active'] ?? 1)); echo ""; echo ""; echo ""; echo "" . htmlspecialchars(Entity::getTypeName(1)) . ""; echo ""; Entity::dropdown([ 'name' => 'entities_id', 'value' => (int) ($this->fields['entities_id'] ?? ($_SESSION['glpiactive_entity'] ?? 0)), ]); echo ""; echo "" . htmlspecialchars(__('Recursive')) . ""; echo ""; Dropdown::showYesNo('is_recursive', (int) ($this->fields['is_recursive'] ?? 0)); echo ""; echo ""; echo ""; echo "" . htmlspecialchars(Location::getTypeName(1)) . ""; echo ""; Location::dropdown([ 'name' => 'locations_id', 'value' => (int) ($this->fields['locations_id'] ?? 0), ]); echo "
"; echo htmlspecialchars( __('Associate the server with the main/root location. Assets in sub-locations will use this root location server.', 'urbackup') ); echo ""; echo ""; echo "" . htmlspecialchars(__('Protocol', 'urbackup')) . ""; echo ""; Dropdown::showFromArray( 'protocol', [ 'http' => 'HTTP', 'https' => 'HTTPS', ], [ 'value' => $this->fields['protocol'] ?? 'http', ] ); echo ""; echo ""; echo ""; echo "" . htmlspecialchars(__('IP address', 'urbackup')) . ""; echo ""; echo Html::input('ip_address', [ 'value' => $this->fields['ip_address'] ?? '', 'size' => 40, ]); echo ""; echo "" . htmlspecialchars(__('Network port', 'urbackup')) . ""; echo ""; echo Html::input('port', [ 'value' => $this->fields['port'] ?? 55414, 'type' => 'number', 'min' => 1, 'max' => 65535, ]); echo ""; echo ""; echo ""; echo "" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . ""; echo ""; echo Html::input('server_version', [ 'value' => $this->fields['server_version'] ?? '', 'size' => 30, ]); echo ""; echo "" . htmlspecialchars(__('Ignore SSL verification', 'urbackup')) . ""; echo ""; Dropdown::showYesNo('ignore_ssl', (int) ($this->fields['ignore_ssl'] ?? 0)); echo ""; echo ""; echo ""; echo "" . htmlspecialchars(__('API username', 'urbackup')) . ""; echo ""; echo Html::input('api_username', [ 'value' => $this->fields['api_username'] ?? '', 'size' => 40, 'autocomplete' => 'off', ]); echo ""; echo "" . htmlspecialchars(__('API password', 'urbackup')) . ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo "" . htmlspecialchars(__('Comments')) . ""; echo ""; echo ""; echo ""; echo ""; if ($ID > 0) { echo ""; echo "" . htmlspecialchars(__('UrBackup web interface', 'urbackup')) . ""; echo ""; $url = $this->getWebInterfaceUrl(); if ($url !== '#') { echo ""; echo htmlspecialchars(__('Open UrBackup interface', 'urbackup')); echo ""; } else { echo htmlspecialchars(__('No URL available', 'urbackup')); } echo ""; echo ""; $apiStatus = (int) ($this->fields['last_api_status'] ?? 0); echo ""; echo "" . htmlspecialchars(__('API connection status', 'urbackup')) . "
" . htmlspecialchars(__('Click Save to test connection', 'urbackup')) . ""; echo ""; if ($apiStatus === 1) { echo ' ' . htmlspecialchars(__('API connection OK', 'urbackup')) . ''; } else { echo ' ' . htmlspecialchars(__('API connection failed', 'urbackup')) . ''; if (!empty($this->fields['last_api_message'])) { echo '
' . htmlspecialchars((string) $this->fields['last_api_message']) . ''; } } echo ""; echo ""; } $this->showFormButtons($options); return true; } /** * Get web interface URL. * * @return string */ public function getWebInterfaceUrl(): string { $protocol = (string) ($this->fields['protocol'] ?? 'http'); $ip = (string) ($this->fields['ip_address'] ?? ''); $port = (int) ($this->fields['port'] ?? 55414); if ($ip === '') { return '#'; } return sprintf('%s://%s:%d', $protocol, $ip, $port); } /** * Get active servers assigned to a root location. * * @param int $locations_id Root location ID * * @return array */ public static function getActiveServersForRootLocation(int $locations_id): array { global $DB; $servers = []; if ($locations_id <= 0) { return $servers; } if (!$DB->tableExists(self::getTable())) { return $servers; } $iterator = $DB->request([ 'FROM' => self::getTable(), 'WHERE' => [ 'locations_id' => $locations_id, 'is_active' => 1, ], 'ORDER' => 'name', ]); foreach ($iterator as $row) { $servers[(int) $row['id']] = (string) $row['name']; } return $servers; } public function prepareInputForAdd(mixed $input): mixed { return $this->prepareInputForUpdate($input); } public function prepareInputForUpdate(mixed $input): mixed { if (!is_array($input)) { return $input; } if (!empty($input['id']) && (int) $input['id'] > 0) { $server = new self(); if ($server->getFromDB((int) $input['id'])) { $serverFields = $server->fields; $ip = $input['ip_address'] ?? $serverFields['ip_address'] ?? ''; $port = $input['port'] ?? $serverFields['port'] ?? 55414; $protocol = $input['protocol'] ?? $serverFields['protocol'] ?? 'http'; $apiUsername = $input['api_username'] ?? $serverFields['api_username'] ?? ''; $apiPassword = $input['api_password'] ?? $serverFields['api_password'] ?? ''; $ignoreSsl = $input['ignore_ssl'] ?? $serverFields['ignore_ssl'] ?? 0; if ($ip !== '') { $tmpServer = new self(); $tmpServer->fields = [ 'id' => (int) $input['id'], 'ip_address' => $ip, 'port' => $port, 'protocol' => $protocol, 'api_username' => $apiUsername, 'api_password' => $apiPassword, 'ignore_ssl' => $ignoreSsl, ]; try { $client = new UrbackupApiClient($tmpServer); $result = $client->testConnection(); $input['last_api_status'] = $result['success'] ? 1 : 0; $input['last_api_message'] = $result['message'] ?? ''; $input['last_api_check'] = date('Y-m-d H:i:s'); } catch (\Throwable $e) { $input['last_api_status'] = 0; $input['last_api_message'] = $e->getMessage(); $input['last_api_check'] = date('Y-m-d H:i:s'); } } } } return $input; } public function testApiConnection(): array { $ip = (string) ($this->fields['ip_address'] ?? ''); if ($ip === '') { return [ 'status' => 'no_ip', 'html' => '' . htmlspecialchars(__('No IP address configured', 'urbackup')) . '', ]; } try { $client = new UrbackupApiClient($this); $result = $client->testConnection(); $this->update([ 'id' => (int) $this->fields['id'], 'last_api_status' => $result['success'] ? 1 : 0, 'last_api_message' => $result['message'] ?? '', 'last_api_check' => date('Y-m-d H:i:s'), ]); if ($result['success']) { return [ 'status' => 'ok', 'html' => ' ' . htmlspecialchars(__('API connection OK', 'urbackup')) . '', ]; } return [ 'status' => 'failed', 'html' => ' ' . htmlspecialchars(__('API connection failed', 'urbackup')) . '
' . '' . htmlspecialchars($result['message'] ?? '') . '', ]; } catch (\Throwable $e) { $message = $e->getMessage(); $isUnreachable = $this->isNetworkError($message); return [ 'status' => $isUnreachable ? 'unreachable' : 'failed', 'html' => '' . ' ' . htmlspecialchars($isUnreachable ? __('Server unreachable', 'urbackup') : __('API connection failed', 'urbackup')) . '
' . '' . htmlspecialchars($message) . '', ]; } } private function isNetworkError(string $message): bool { $networkKeywords = [ 'timeout', 'could not resolve host', 'couldn\'t connect to host', 'connection refused', 'connection timed out', 'network is unreachable', 'no route to host', 'ssl', 'certificate', 'curl error', 'request failed', 'returned HTTP status', 'returned non-JSON response', 'problem with the ssl certificate', 'ssl certificate problem', 'ssl connect error', 'ssl wrong version', ]; $lowerMessage = strtolower($message); foreach ($networkKeywords as $keyword) { if (str_contains($lowerMessage, strtolower($keyword))) { return true; } } if (preg_match('/http status [45]\d{2}/', $lowerMessage)) { return true; } return false; } private static function renderOnlineBadge(mixed $online, mixed $statusString): string { $online = match (true) { $online === true || $online === 1 || $online === '1' || $online === 'true' => 1, $online === false || $online === 0 || $online === '0' || $online === 'false' => 0, default => null, }; $parts = []; if ($online === 1) { $parts[] = ' ' . htmlspecialchars(__('Online', 'urbackup')) . ''; } elseif ($online === 0) { $parts[] = ' ' . htmlspecialchars(__('Offline', 'urbackup')) . ''; } else { $parts[] = '-'; } $statusString = is_string($statusString) ? trim((string) $statusString) : ''; if ($statusString !== '') { $badgeClass = 'secondary'; if (strtolower($statusString) === 'ok') { $badgeClass = 'success'; } elseif (in_array(strtolower($statusString), ['minor_problems', 'minor problems'], true)) { $badgeClass = 'warning'; } elseif (in_array(strtolower($statusString), ['major_problems', 'major problems', 'error'], true)) { $badgeClass = 'danger'; } elseif (strtolower($statusString) === 'paused') { $badgeClass = 'secondary'; } $parts[] = '' . htmlspecialchars($statusString) . ''; } return implode(' ', $parts); } private static function formatLastBackup(mixed $value): string { if ($value === null || $value === '' || $value === 0 || $value === '0') { return ''; } if (is_numeric($value)) { $timestamp = (int) $value; if ($timestamp > 0 && $timestamp < 2000000000) { return date('Y-m-d H:i:s', $timestamp); } return (string) $value; } return (string) $value; } public static function showLinkedClientsTab(Server $server): void { global $DB; $apiStatus = (int) ($server->fields['last_api_status'] ?? 0); if ($apiStatus !== 1) { echo '
'; echo htmlspecialchars(__('API connection not working. Save server to test connection.', 'urbackup')); echo '
'; return; } $iterator = $DB->request([ 'FROM' => 'glpi_plugin_urbackup_serverassets', 'WHERE' => [ 'plugin_urbackup_servers_id' => (int) $server->fields['id'], ], ]); $linkedAssets = []; foreach ($iterator as $row) { $linkedAssets[] = $row; } if (count($linkedAssets) === 0) { echo '
'; echo htmlspecialchars(__('No linked assets', 'urbackup')); echo '
'; return; } try { $client = new UrbackupApiClient($server); $urbackupClients = $client->getStatus(); if (empty($urbackupClients)) { echo '
'; echo htmlspecialchars(__('No clients found on UrBackup server', 'urbackup')); echo '
'; return; } } catch (\Throwable $e) { echo '
'; echo 'API Error: ' . htmlspecialchars($e->getMessage()); echo '
'; return; } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ($linkedAssets as $link) { $glpiItem = null; if (!empty($link['itemtype']) && !empty($link['items_id'])) { $itemClass = $link['itemtype']; if (class_exists($itemClass)) { $glpiItem = new $itemClass(); $glpiItem->getFromDB((int) $link['items_id']); } } $clientName = $glpiItem ? ($glpiItem->fields['name'] ?? '') : ''; $urbackupClient = null; foreach ($urbackupClients as $uc) { $ucName = (string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? ''); if (strcasecmp($ucName, $clientName) === 0) { $urbackupClient = $uc; break; } } $online = $urbackupClient ? ($urbackupClient['online'] ?? null) : null; $apiStatusString = $urbackupClient ? ($urbackupClient['status'] ?? '') : ''; $statusHtml = self::renderOnlineBadge($online, $apiStatusString); $lastBackupRaw = $urbackupClient ? ($urbackupClient['lastbackup'] ?? $urbackupClient['lastbackup_time'] ?? $urbackupClient['last_backup'] ?? $urbackupClient['last_backup_time'] ?? $urbackupClient['file_lastbackup'] ?? $urbackupClient['image_lastbackup'] ?? '') : ''; $lastBackup = self::formatLastBackup($lastBackupRaw); $clientIp = $urbackupClient ? ($urbackupClient['client_ip'] ?? $urbackupClient['ip'] ?? $urbackupClient['ip_address'] ?? $urbackupClient['clientaddress'] ?? '') : ''; $clientVersion = $urbackupClient ? ($urbackupClient['client_version_string'] ?? $urbackupClient['client_version'] ?? '-') : '-'; $urbackupClientName = $urbackupClient ? ($urbackupClient['name'] ?? '-') : '-'; $itemTypeLabel = !empty($link['itemtype']) ? (class_exists($link['itemtype']) ? $link['itemtype']::getTypeName(1) : $link['itemtype']) : ''; $itemUrl = $glpiItem ? $glpiItem->getLinkURL() : ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo ''; echo '
' . htmlspecialchars(__('Asset', 'urbackup')) . '' . htmlspecialchars(__('Name', 'urbackup')) . '' . htmlspecialchars(__('Client name', 'urbackup')) . '' . htmlspecialchars(__('Version', 'urbackup')) . '' . htmlspecialchars(__('Status', 'urbackup')) . '' . htmlspecialchars(__('Last backup', 'urbackup')) . '' . htmlspecialchars(__('IP address', 'urbackup')) . '
' . htmlspecialchars($itemTypeLabel . ' #' . $link['items_id']) . '' . ($itemUrl ? '' . htmlspecialchars($clientName) . '' : htmlspecialchars($clientName)) . '' . htmlspecialchars($urbackupClientName) . '' . htmlspecialchars($clientVersion) . '' . $statusHtml . '' . htmlspecialchars($lastBackup ?: '-') . '' . htmlspecialchars($clientIp ?: '-') . '
'; } public static function showUnlinkedClientsTab(Server $server): void { $apiStatus = (int) ($server->fields['last_api_status'] ?? 0); if ($apiStatus !== 1) { echo '
'; echo htmlspecialchars(__('API connection not working. Save server to test connection.', 'urbackup')); echo '
'; return; } global $DB; $iterator = $DB->request([ 'FROM' => 'glpi_plugin_urbackup_serverassets', ]); $linkedNames = []; foreach ($iterator as $row) { $assetName = ServerAsset::getAssetName($row['itemtype'], (int) $row['items_id']); if ($assetName !== '') { $linkedNames[] = strtolower($assetName); } } try { $client = new UrbackupApiClient($server); $urbackupClients = $client->getStatus(); } catch (\Throwable $e) { echo '
'; echo htmlspecialchars($e->getMessage()); echo '
'; return; } $unlinkedClients = []; foreach ($urbackupClients as $uc) { $name = strtolower((string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? '')); if (!in_array($name, $linkedNames, true)) { $unlinkedClients[] = $uc; } } if (count($unlinkedClients) === 0) { echo '
'; echo htmlspecialchars(__('No unlinked clients found on UrBackup server', 'urbackup')); echo '
'; return; } $serverLocationId = (int) ($server->fields['locations_id'] ?? 0); $linkableAssets = []; if ($serverLocationId > 0) { $itemtypes = Config::getEnabledItemtypes(); foreach ($unlinkedClients as $uc) { $clientName = (string) ($uc['name'] ?? ''); if ($clientName === '') { continue; } $clientNameLower = strtolower($clientName); foreach ($itemtypes as $itemtype) { if (!class_exists($itemtype)) { continue; } $assetItem = new $itemtype(); if (!$assetItem instanceof CommonDBTM) { continue; } $table = $assetItem->getTable(); if (!$DB->tableExists($table)) { continue; } $assetIterator = $DB->request([ 'FROM' => $table, 'WHERE' => [ 'name' => $clientName, 'is_deleted' => 0, ], 'LIMIT' => 1, ]); foreach ($assetIterator as $assetRow) { $assetLocationId = (int) ($assetRow['locations_id'] ?? 0); $rootLocationId = LocationHelper::getRootLocationId($assetLocationId); if ($rootLocationId > 0 && $rootLocationId === $serverLocationId) { $linkableAssets[$clientNameLower] = [ 'itemtype' => $itemtype, 'items_id' => (int) $assetRow['id'], ]; } break; } } } } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ($unlinkedClients as $uc) { $clientName = (string) ($uc['name'] ?? 'Unknown'); $clientNameLower = strtolower($clientName); $online = $uc['online'] ?? null; $apiStatusString = $uc['status'] ?? ''; $statusHtml = self::renderOnlineBadge($online, $apiStatusString); $lastBackupRaw = $uc['lastbackup'] ?? $uc['lastbackup_time'] ?? $uc['last_backup'] ?? $uc['last_backup_time'] ?? $uc['file_lastbackup'] ?? $uc['image_lastbackup'] ?? ''; $lastBackup = self::formatLastBackup($lastBackupRaw); $clientIp = $uc['client_ip'] ?? $uc['ip'] ?? $uc['ip_address'] ?? $uc['clientaddress'] ?? ''; $clientVersion = $uc['client_version_string'] ?? $uc['client_version'] ?? '-'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo ''; echo '
' . htmlspecialchars(__('Name', 'urbackup')) . '' . htmlspecialchars(__('Version', 'urbackup')) . '' . htmlspecialchars(__('Status', 'urbackup')) . '' . htmlspecialchars(__('Last backup', 'urbackup')) . '' . htmlspecialchars(__('IP address', 'urbackup')) . '' . htmlspecialchars(__('Actions', 'urbackup')) . '
' . htmlspecialchars($clientName) . '' . htmlspecialchars($clientVersion) . '' . $statusHtml . '' . htmlspecialchars($lastBackup ?: '-') . '' . htmlspecialchars($clientIp ?: '-') . ''; if (isset($linkableAssets[$clientNameLower])) { $match = $linkableAssets[$clientNameLower]; $formAction = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php'; echo '
'; echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); echo Html::hidden('itemtype', ['value' => $match['itemtype']]); echo Html::hidden('items_id', ['value' => $match['items_id']]); echo Html::hidden('id', ['value' => (int) $server->fields['id']]); echo ''; Html::closeForm(); } else { echo ''; } echo '
'; } }