"; echo htmlspecialchars(__('You do not have permission to view UrBackup information.', 'urbackup')); echo ""; return true; } $itemtype = $item::class; $items_id = (int) ($item->fields['id'] ?? 0); $link = ServerAsset::getLinkForAsset($itemtype, $items_id, true); echo "
"; if ($link === null) { self::showNoServerLinkedBlock($item); } else { self::showServerLinkedBlock($item, $link); } echo "
"; return true; } /** * Show block when no server is linked. * * @param CommonDBTM $item Asset item * * @return void */ private static function showNoServerLinkedBlock(CommonDBTM $item): void { $asset_location_id = LocationHelper::getAssetLocationId($item); $root_location_id = LocationHelper::getRootLocationIdForAsset($item); $is_sub_location = LocationHelper::assetIsInSubLocation($item); $servers = LocationHelper::getAvailableServersForAsset($item); echo "
"; echo htmlspecialchars(__('No UrBackup server linked.', 'urbackup')); echo "
"; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; if ($is_sub_location) { echo ""; echo ""; echo ""; } if (count($servers) === 0) { echo ""; echo ""; echo ""; echo "
" . htmlspecialchars(__('UrBackup server selection', 'urbackup')) . "
" . htmlspecialchars(__('Asset location ID', 'urbackup')) . "" . htmlspecialchars((string) $asset_location_id) . "
" . htmlspecialchars(__('Root location ID', 'urbackup')) . "" . htmlspecialchars((string) $root_location_id) . "
"; echo htmlspecialchars( __('The asset is in a sub-location. The plugin will use the server assigned to the root location.', 'urbackup') ); echo "
"; echo "
"; echo htmlspecialchars(__('No UrBackup server available for the root location of this asset.', 'urbackup')); echo "
"; echo "
"; return; } if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) { echo ""; echo ""; echo htmlspecialchars(__('A server is available, but you do not have permission to link this asset.', 'urbackup')); echo ""; echo ""; echo ""; return; } echo ""; echo "" . htmlspecialchars(__('Available servers for root location', 'urbackup')) . ""; echo ""; echo "
"; echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); echo Html::hidden('itemtype', ['value' => $item::class]); echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); Dropdown::showFromArray( 'plugin_urbackup_servers_id', $servers, [ 'value' => 0, ] ); echo Html::submit(__('Connect', 'urbackup'), [ 'name' => 'connect', 'class' => 'btn btn-primary', ]); Html::closeForm(); echo ""; echo ""; echo ""; } /** * Show block when server is linked. * * @param CommonDBTM $item Asset item * @param array $link Link row * * @return void */ private static function showServerLinkedBlock(CommonDBTM $item, array $link): void { $server = new Server(); $server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0); if ($server_id <= 0 || !$server->getFromDB($server_id)) { echo "
"; echo htmlspecialchars(__('The linked UrBackup server no longer exists.', 'urbackup')); echo "
"; return; } $api_data = self::loadApiData($item, $server, $link); echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo "
" . htmlspecialchars(__('UrBackup status', 'urbackup')) . "
" . htmlspecialchars(__('Linked server', 'urbackup')) . "" . $server->getLink() . "" . htmlspecialchars(__('IP address', 'urbackup')) . "" . htmlspecialchars((string) $server->fields['ip_address']) . "
" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . "" . htmlspecialchars((string) ($server->fields['server_version'] ?? '')) . "" . htmlspecialchars(__('Client name', 'urbackup')) . "" . htmlspecialchars(ServerAsset::getAssetName($item::class, (int) $item->fields['id'])) . "
"; if ($api_data['error'] !== '') { echo "
"; echo htmlspecialchars($api_data['error']); echo "
"; } if ($api_data['ip_warning'] !== '') { echo "
"; echo htmlspecialchars($api_data['ip_warning']); echo "
"; } self::showInternalTabs($item, $server, $link, $api_data); } /** * Load API data for asset tab. * * @param CommonDBTM $item Asset * @param Server $server Server * @param array $link Link * * @return array */ private static function loadApiData(CommonDBTM $item, Server $server, array $link): array { $client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']); $asset_ip = ServerAsset::extractAssetIp($item); $data = [ 'client_found' => false, 'client_status' => [], 'client_settings' => [], 'authkey' => '', 'recent_backups' => [], 'logs' => [], 'error' => '', 'ip_warning' => '', ]; try { $api = new UrbackupApiClient($server); $client_status = $api->getClientStatusByName($client_name); if ($client_status !== null) { $data['client_found'] = true; $data['client_status'] = $client_status; $urbackup_ip = (string) ( $client_status['ip'] ?? $client_status['ip_address'] ?? $client_status['addr'] ?? '' ); if ($asset_ip !== '' && $urbackup_ip !== '' && $asset_ip !== $urbackup_ip) { $data['ip_warning'] = sprintf( __('Client name matches, but GLPI IP "%s" differs from UrBackup IP "%s".', 'urbackup'), $asset_ip, $urbackup_ip ); } $data['client_settings'] = $api->getClientSettings($client_name); $data['authkey'] = $api->getClientAuthKey($client_name); $data['recent_backups'] = $api->getRecentBackups($client_name, 10); $data['logs'] = $api->getClientLogs($client_name, 50); } } catch (Throwable $e) { $data['error'] = $e->getMessage(); } return $data; } /** * Show internal sections. * * @param CommonDBTM $item Asset * @param Server $server Server * @param array $link Link * @param array $api_data API data * * @return void */ private static function showInternalTabs( CommonDBTM $item, Server $server, array $link, array $api_data ): void { echo "
"; echo "

" . htmlspecialchars(__('State', 'urbackup')) . "

"; self::showStateSection($server, $link, $api_data); echo "

" . htmlspecialchars(__('Actions', 'urbackup')) . "

"; self::showActionsSection($item, $server, $link, $api_data); echo "

" . htmlspecialchars(__('Info / Log', 'urbackup')) . "

"; self::showInfoLogSection($api_data); echo "
"; } /** * Show state section. * * @param Server $server Server * @param array $link Link * @param array $api_data API data * * @return void */ private static function showStateSection(Server $server, array $link, array $api_data): void { $status = $api_data['client_status']; $settings = $api_data['client_settings']; echo ""; echo ""; if (!$api_data['client_found']) { echo ""; echo ""; echo ""; echo "
" . htmlspecialchars(__('Client state', 'urbackup')) . "
"; echo htmlspecialchars(__('Client not found on UrBackup server.', 'urbackup')); echo "
"; return; } $internetMode = self::extractSettingValue($settings['internet_mode_enabled'] ?? $settings['internet_mode'] ?? null, 0); $internetModeDisplay = ((int) $internetMode === 1) ? '' . __('Yes', 'urbackup') . '' : '' . __('No', 'urbackup') . ''; $rows = [ __('Client version', 'urbackup') => $status['client_version_string'] ?? $status['client_version'] ?? $status['version'] ?? '-', __('Online / Offline', 'urbackup') => self::formatOnlineStatus($status), __('Last file backup', 'urbackup') => self::formatTimestamp($status['file_lastbackup'] ?? $status['lastbackup'] ?? $status['last_file_backup'] ?? ''), __('Last image backup', 'urbackup') => self::formatTimestamp($status['image_lastbackup'] ?? $status['lastbackup_image'] ?? $status['last_image_backup'] ?? ''), __('Last file backup result', 'urbackup') => self::formatBoolStatus($status['file_ok'] ?? null), __('Last image backup result', 'urbackup') => self::formatBoolStatus($status['image_ok'] ?? null), __('Current activities', 'urbackup') => $status['status'] ?? $status['activity'] ?? '-', __('Internet mode', 'urbackup') => $internetModeDisplay, ]; foreach ($rows as $label => $value) { $displayValue = is_array($value) ? json_encode($value) : (string) $value; echo ""; echo "" . htmlspecialchars((string) $label) . ""; echo "" . $displayValue . ""; echo ""; } echo ""; echo "" . htmlspecialchars(__('Internet authentication key', 'urbackup')) . ""; echo ""; $authKey = (string) $api_data['authkey']; if ($authKey === '') { echo '-'; } else { echo ''; echo '
'; echo ''; echo ''; echo '
'; } echo ""; echo ""; echo ""; echo '
'; echo '' . htmlspecialchars(__('API raw data (debug)', 'urbackup')) . ''; echo '
';
        echo "--- client_settings ---\n\n";
        echo htmlspecialchars(json_encode($settings, JSON_PRETTY_PRINT));
        echo "\n\n--- client_status ---\n\n";
        echo htmlspecialchars(json_encode($status, JSON_PRETTY_PRINT));
        echo "\n\n--- recent_backups ---\n\n";
        echo htmlspecialchars(json_encode($api_data['recent_backups'] ?? [], JSON_PRETTY_PRINT));
        echo '
'; echo '
'; } /** * Show actions section. * * @param CommonDBTM $item Asset * @param Server $server Server * @param array $link Link * @param array $api_data API data * * @return void */ private static function showActionsSection( CommonDBTM $item, Server $server, array $link, array $api_data ): void { echo ""; echo ""; if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) { echo ""; echo ""; echo ""; echo "
" . htmlspecialchars(__('Available actions', 'urbackup')) . "
"; echo htmlspecialchars(__('You do not have permission for UrBackup actions.', 'urbackup')); echo "
"; return; } if (!$api_data['client_found'] && Profile::canCurrentUser(CREATE)) { echo ""; echo "" . htmlspecialchars(__('Create client in UrBackup', 'urbackup')) . ""; echo ""; self::showActionButton($item, 'create_client', __('Create client in UrBackup', 'urbackup'), 'btn btn-primary'); echo ""; echo ""; } if (Profile::canCurrentUser(UPDATE)) { echo ""; echo "" . htmlspecialchars(__('Internet mode', 'urbackup')) . ""; echo ""; self::showInternetModeForm($item, $api_data); echo ""; echo ""; echo ""; echo "" . htmlspecialchars(__('Default directories', 'urbackup')) . ""; echo ""; self::showDefaultDirsForm($item, $api_data); echo ""; echo ""; echo ""; echo "" . htmlspecialchars(__('Backup commands', 'urbackup')) . ""; echo ""; self::showActionButton($item, 'incremental_file_backup', __('Incremental file backup', 'urbackup'), 'btn btn-secondary'); self::showActionButton($item, 'full_file_backup', __('Full file backup', 'urbackup'), 'btn btn-secondary'); self::showActionButton($item, 'incremental_image_backup', __('Incremental image backup', 'urbackup'), 'btn btn-secondary'); self::showActionButton($item, 'full_image_backup', __('Full image backup', 'urbackup'), 'btn btn-secondary'); echo ""; echo ""; echo ""; echo "" . htmlspecialchars(__('Disconnect client', 'urbackup')) . ""; echo ""; self::showDisconnectButton($item); echo ""; echo ""; } if (Profile::canCurrentUser(PURGE)) { echo ""; echo "" . htmlspecialchars(__('Delete client from UrBackup server', 'urbackup')) . ""; echo ""; echo "
"; echo htmlspecialchars( __('The client deletion will be queued on the UrBackup server and may require up to 24 hours.', 'urbackup') ); echo "
"; self::showActionButton($item, 'delete_client', __('Delete client from UrBackup server', 'urbackup'), 'btn btn-danger'); echo ""; echo ""; } echo ""; } /** * Show info/log section. * * @param array $api_data API data * * @return void */ private static function formatBytes(mixed $bytes): string { $bytes = (float) ($bytes ?? 0); if ($bytes <= 0) { return '-'; } $units = ['B', 'KB', 'MB', 'GB', 'TB']; $i = floor(log($bytes, 1024)); $i = min((int) $i, count($units) - 1); return sprintf('%.1f %s', $bytes / pow(1024, $i), $units[$i]); } /** * Show info/log section. * * @param array $api_data API data * * @return void */ private static function showInfoLogSection(array $api_data): void { $recent_backups = $api_data['recent_backups'] ?? []; usort($recent_backups, function ($a, $b) { $tsA = (int) ($a['time'] ?? $a['backuptime'] ?? $a['backup_time'] ?? $a['created'] ?? $a['start_time'] ?? 0); $tsB = (int) ($b['time'] ?? $b['backuptime'] ?? $b['backup_time'] ?? $b['created'] ?? $b['start_time'] ?? 0); return $tsB - $tsA; }); echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; foreach ($recent_backups as $backup) { $incremental = (int) ($backup['incremental'] ?? 0); $timestamp = $backup['time'] ?? $backup['backuptime'] ?? $backup['backup_time'] ?? $backup['created'] ?? $backup['start_time'] ?? 0; $dateFormatted = self::formatTimestamp($timestamp); echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } if (count($api_data['recent_backups']) === 0) { echo ""; } echo "
" . htmlspecialchars(__('Recent backups', 'urbackup')) . "
" . htmlspecialchars(__('Type')) . "" . htmlspecialchars(__('Date')) . "" . htmlspecialchars(__('Result')) . "" . htmlspecialchars(__('Size')) . "" . htmlspecialchars(__('Incremental', 'urbackup')) . "" . htmlspecialchars(__('Backup ID', 'urbackup')) . "
" . htmlspecialchars((string) ($backup['backup_type'] ?? '')) . "" . htmlspecialchars($dateFormatted) . " " . htmlspecialchars(__('Success', 'urbackup')) . "" . htmlspecialchars(self::formatBytes($backup['size'] ?? $backup['size_bytes'] ?? 0)) . "" . htmlspecialchars($incremental === 1 ? __('Yes', 'urbackup') : __('No', 'urbackup')) . "" . htmlspecialchars((string) ($backup['backupid'] ?? $backup['id'] ?? '-')) . "
"; echo htmlspecialchars(__('No recent backup information available.', 'urbackup')); echo "
"; echo "
"; $logs = array_reverse($api_data['logs'] ?? []); echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; foreach ($logs as $log) { echo ""; echo ""; echo ""; echo ""; echo ""; } if (count($api_data['logs']) === 0) { echo ""; } echo "
" . htmlspecialchars(__('Client logs', 'urbackup')) . "
" . htmlspecialchars(__('Date')) . "" . htmlspecialchars(__('Level')) . "" . htmlspecialchars(__('Message')) . "
" . htmlspecialchars(self::formatTimestamp($log['time'] ?? $log['created'] ?? '')) . "" . htmlspecialchars((string) ($log['level'] ?? $log['severity'] ?? '')) . "" . htmlspecialchars((string) ($log['message'] ?? $log['msg'] ?? $log['text'] ?? '')) . "
"; echo htmlspecialchars(__('No client logs available.', 'urbackup')); echo "
"; } /** * Show generic UrBackup action button. * * @param CommonDBTM $item Asset * @param string $action Action * @param string $label Button label * @param string $class CSS class * * @return void */ private static function showActionButton(CommonDBTM $item, string $action, string $label, string $class): void { echo ""; echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); echo Html::hidden('itemtype', ['value' => $item::class]); echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); echo Html::hidden('urbackup_action', ['value' => $action]); echo Html::submit($label, [ 'name' => 'execute', 'class' => $class, ]); Html::closeForm(); echo " "; } /** * Show internet mode form. * * @param CommonDBTM $item Asset * @param array $api_data API data * * @return void */ private static function showInternetModeForm(CommonDBTM $item, array $api_data): void { $settings = $api_data['client_settings']; $current = (int) self::extractSettingValue( $settings['internet_mode_enabled'] ?? $settings['internet_mode'] ?? null, 0 ); echo ""; echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); echo Html::hidden('itemtype', ['value' => $item::class]); echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); echo Html::hidden('urbackup_action', ['value' => 'set_internet_mode']); echo Html::hidden('execute', ['value' => '1']); echo Html::hidden('internet_mode', ['value' => '0']); echo '
'; echo ''; echo ''; echo '
'; Html::closeForm(); } /** * Show default directories form. * * @param CommonDBTM $item Asset * @param array $api_data API data * * @return void */ private static function showDefaultDirsForm(CommonDBTM $item, array $api_data): void { $settings = $api_data['client_settings']; $raw = $settings['default_dirs'] ?? ''; $display = ''; if (is_array($raw)) { // API returns struct: {"use":N,"value":"paths","value_client":"","value_group":""} $display = (string) ($raw['value'] ?? ''); } else { $display = (string) $raw; } echo ""; echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); echo Html::hidden('itemtype', ['value' => $item::class]); echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); echo Html::hidden('urbackup_action', ['value' => 'set_default_dirs']); echo "
"; echo '
'; echo 'raw: ' . htmlspecialchars(json_encode($raw)); echo '
'; echo Html::submit(__('Save'), [ 'name' => 'execute', 'class' => 'btn btn-primary', ]); Html::closeForm(); } /** * Show disconnect button. * * @param CommonDBTM $item Asset * * @return void */ private static function showDisconnectButton(CommonDBTM $item): void { echo ""; echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); echo Html::hidden('itemtype', ['value' => $item::class]); echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); echo Html::submit(__('Disconnect', 'urbackup'), [ 'name' => 'disconnect', 'class' => 'btn btn-warning', ]); Html::closeForm(); } /** * Format online status. * * @param array $status Status * * @return string */ private static function formatOnlineStatus(array $status): string { $online = $status['online'] ?? $status['is_online'] ?? null; return match (true) { $online === true || $online === 1 || $online === '1' || $online === 'true' => __('Online', 'urbackup'), $online === false || $online === 0 || $online === '0' || $online === 'false' => __('Offline', 'urbackup'), default => '', }; } /** * Format bool status. * * @param mixed $value Value * * @return string */ private static function formatBoolStatus(mixed $value): string { return match (true) { $value === true || $value === 1 || $value === '1' || $value === 'true' => __('OK', 'urbackup'), $value === false || $value === 0 || $value === '0' || $value === 'false' => __('Failed', 'urbackup'), default => '', }; } /** * Format timestamp-like value. * * @param mixed $value Timestamp * * @return string */ private static function formatTimestamp(mixed $value): string { if ($value === null || $value === '' || $value === 0 || $value === '0') { return ''; } $ts = (int) $value; if ($ts > 0 && $ts < 2000000000) { // Unix timestamp return Html::convDateTime(date('Y-m-d H:i:s', $ts)); } $str = trim((string) $value); if ($str === '') { return ''; } // ISO date string like "2024-01-15T10:30:00" or "2024-01-15 10:30:00" $normalized = str_replace('T', ' ', $str); if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/', $normalized)) { return Html::convDateTime($normalized); } // Plain date like "2024-01-15" if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $str)) { return Html::convDate($str); } return $str; } public static function startBackup(CommonDBTM $item, string $type): bool { $link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false); if ($link === null) { return false; } $server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0); if ($server_id <= 0) { return false; } $server = new Server(); if (!$server->getFromDB($server_id)) { return false; } $client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']); if ($client_name === '') { return false; } try { $api = new UrbackupApiClient($server); switch ($type) { case 'file': return $api->startIncrementalFileBackup($client_name); case 'image': return $api->startIncrementalImageBackup($client_name); } } catch (\Throwable $e) { return false; } return false; } public static function saveInternetMode(CommonDBTM $item, bool $enabled): bool { $link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false); if ($link === null) { return false; } $server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0); if ($server_id <= 0) { return false; } $server = new Server(); if (!$server->getFromDB($server_id)) { return false; } $client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']); if ($client_name === '') { return false; } try { $api = new UrbackupApiClient($server); return $api->saveInternetMode($client_name, $enabled); } catch (\Throwable $e) { return false; } } public static function saveDefaultDirs(CommonDBTM $item, string $dirs): bool { $link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false); if ($link === null) { return false; } $server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0); if ($server_id <= 0) { return false; } $server = new Server(); if (!$server->getFromDB($server_id)) { return false; } $client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']); if ($client_name === '') { return false; } try { $api = new UrbackupApiClient($server); return $api->updateClientSettings($client_name, 'default_dirs', trim($dirs)); } catch (\Throwable $e) { return false; } } }