connessione con hardware in assets

This commit is contained in:
test
2026-08-07 14:27:33 +02:00
parent 7c6e244155
commit f575d8cb05
33 changed files with 3489 additions and 1608 deletions
+131 -33
View File
@@ -102,6 +102,8 @@ class AssetTab extends CommonDBTM
echo "<div class='plugin-urbackup-asset-tab'>";
self::showHostServerBlock($item);
if ($link === null) {
self::showNoServerLinkedBlock($item);
} else {
@@ -113,6 +115,40 @@ class AssetTab extends CommonDBTM
return true;
}
/**
* Show the block displaying the UrBackup servers hosted on this asset.
*
* Always visible when the asset hosts one or more UrBackup servers,
* even when the asset is already linked as a client.
*
* @param CommonDBTM $item Asset item
*
* @return void
*/
private static function showHostServerBlock(CommonDBTM $item): void
{
$hostingServers = Server::getServersHostingAsset($item::class, (int) ($item->fields['id'] ?? 0));
if (count($hostingServers) === 0) {
return;
}
echo "<div class='card mb-3'>";
echo "<div class='card-header'><i class='ti ti-server me-1'></i> " .
htmlspecialchars(__('This asset hosts the UrBackup server', 'urbackup')) . "</div>";
echo "<div class='card-body py-3'>";
echo "<ul class='mb-0'>";
foreach ($hostingServers as $serverRow) {
$serverId = (int) ($serverRow['id'] ?? 0);
$serverName = (string) ($serverRow['name'] ?? '');
echo "<li><a href='" . htmlspecialchars(Server::getFormURLWithID($serverId)) . "'>" .
htmlspecialchars($serverName) . "</a></li>";
}
echo "</ul>";
echo "</div>";
echo "</div>";
}
/**
* Show block when no server is linked.
*
@@ -218,24 +254,88 @@ class AssetTab extends CommonDBTM
$api_data = self::loadApiData($item, $server, $link);
echo "<table class='tab_cadre_fixe'>";
echo "<tr><th colspan='4'>" . htmlspecialchars(__('UrBackup status', 'urbackup')) . "</th></tr>";
$ip = (string) ($server->fields['ip_address'] ?? '');
$port = (int) ($server->fields['port'] ?? 0);
$protocol = (string) ($server->fields['protocol'] ?? 'http');
$version = (string) ($server->fields['server_version'] ?? '');
$api_ok = (int) ($server->fields['last_api_status'] ?? 0) === 1;
$api_message = (string) ($server->fields['last_api_message'] ?? '');
$last_check = (string) ($server->fields['last_api_check'] ?? '');
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Linked server', 'urbackup')) . "</td>";
echo "<td>" . $server->getLink() . "</td>";
echo "<td>" . htmlspecialchars(__('IP address', 'urbackup')) . "</td>";
echo "<td>" . htmlspecialchars((string) $server->fields['ip_address']) . "</td>";
echo "</tr>";
echo "<div class='card mb-3'>";
echo "<div class='card-header'><i class='ti ti-cloud-up me-1'></i> "
. htmlspecialchars(__('UrBackup status', 'urbackup')) . "</div>";
echo "<div class='card-body py-3'>";
echo "<div class='row row-cols-1 row-cols-sm-2 row-cols-lg-4 g-2'>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . "</td>";
echo "<td>" . htmlspecialchars((string) ($server->fields['server_version'] ?? '')) . "</td>";
echo "<td>" . htmlspecialchars(__('Client name', 'urbackup')) . "</td>";
echo "<td>" . htmlspecialchars((string) ($item->fields['name'] ?? '')) . "</td>";
echo "</tr>";
// Linked server
echo "<div class='col'>";
echo "<div class='card h-100'>";
echo "<div class='card-body p-3'>";
echo "<div class='text-muted small text-uppercase fw-semibold'>"
. htmlspecialchars(__('Linked server', 'urbackup')) . "</div>";
echo "<div class='fs-5 fw-bold mt-1'>" . $server->getLink() . "</div>";
echo "<div class='text-muted small mt-1'><i class='ti ti-network'></i> ";
echo htmlspecialchars($protocol) . "://<code>" . htmlspecialchars($ip) . "</code>:" . $port;
echo "</div>";
echo "</div>";
echo "</div>";
echo "</div>";
echo "</table>";
// API status
echo "<div class='col'>";
echo "<div class='card h-100'>";
echo "<div class='card-body p-3'>";
echo "<div class='text-muted small text-uppercase fw-semibold'>"
. htmlspecialchars(__('API status', 'urbackup')) . "</div>";
echo "<div class='mt-1'>";
if ($api_ok) {
echo "<span class='badge bg-success'><i class='ti ti-check'></i> "
. htmlspecialchars(__('API connection OK', 'urbackup')) . "</span>";
} else {
echo "<span class='badge bg-danger'><i class='ti ti-x'></i> "
. htmlspecialchars(__('API connection failed', 'urbackup')) . "</span>";
}
echo "</div>";
if ($api_message !== '') {
echo "<div class='text-muted small mt-1'>" . htmlspecialchars($api_message) . "</div>";
}
if ($last_check !== '') {
echo "<div class='text-muted small mt-1'><i class='ti ti-clock'></i> "
. htmlspecialchars(Html::convDateTime($last_check)) . "</div>";
}
echo "</div>";
echo "</div>";
echo "</div>";
// Server version
echo "<div class='col'>";
echo "<div class='card h-100'>";
echo "<div class='card-body p-3'>";
echo "<div class='text-muted small text-uppercase fw-semibold'>"
. htmlspecialchars(__('UrBackup server version', 'urbackup')) . "</div>";
echo "<div class='fs-5 fw-bold mt-1'>"
. ($version !== '' ? htmlspecialchars($version) : '<span class="text-muted">-</span>')
. "</div>";
echo "</div>";
echo "</div>";
echo "</div>";
// Client
echo "<div class='col'>";
echo "<div class='card h-100'>";
echo "<div class='card-body p-3'>";
echo "<div class='text-muted small text-uppercase fw-semibold'>"
. htmlspecialchars(__('Client name', 'urbackup')) . "</div>";
echo "<div class='fs-5 fw-bold mt-1'>"
. htmlspecialchars((string) ($item->fields['name'] ?? '')) . "</div>";
echo "</div>";
echo "</div>";
echo "</div>";
echo "</div>";
echo "</div>";
echo "</div>";
if ($api_data['error'] !== '') {
echo "<div class='alert alert-warning'>";
@@ -249,7 +349,7 @@ class AssetTab extends CommonDBTM
echo "</div>";
}
self::showInternalTabs($item, $server, $link, $api_data);
self::showInternalTabs($item, $server, $api_data);
}
/**
@@ -327,7 +427,6 @@ class AssetTab extends CommonDBTM
*
* @param CommonDBTM $item Asset
* @param Server $server Server
* @param array<string, mixed> $link Link
* @param array<string, mixed> $api_data API data
*
* @return void
@@ -335,7 +434,6 @@ class AssetTab extends CommonDBTM
private static function showInternalTabs(
CommonDBTM $item,
Server $server,
array $link,
array $api_data
): void {
echo "<div class='plugin-urbackup-inner-tabs'>";
@@ -345,7 +443,7 @@ class AssetTab extends CommonDBTM
echo '<ul class="nav nav-tabs" id="urbackupTabs" role="tablist">';
echo '<li class="nav-item" role="presentation">';
echo '<a class="nav-link active" id="state-tab" data-bs-toggle="tab" href="#state" role="tab" aria-selected="true">';
echo htmlspecialchars(__('State', 'urbackup'));
echo htmlspecialchars(__('Client State', 'urbackup'));
echo '</a></li>';
if ($canWrite) {
echo '<li class="nav-item" role="presentation">';
@@ -362,12 +460,12 @@ class AssetTab extends CommonDBTM
echo '<div class="tab-content">';
echo '<div class="tab-pane fade show active" id="state" role="tabpanel">';
self::showStateSection($server, $link, $api_data);
self::showStateSection($api_data);
echo '</div>';
if ($canWrite) {
echo '<div class="tab-pane fade" id="actions" role="tabpanel">';
self::showActionsSection($item, $server, $link, $api_data);
self::showActionsSection($item, $api_data);
echo '</div>';
}
@@ -382,13 +480,11 @@ class AssetTab extends CommonDBTM
/**
* Show state section.
*
* @param Server $server Server
* @param array<string, mixed> $link Link
* @param array<string, mixed> $api_data API data
*
* @return void
*/
private static function showStateSection(Server $server, array $link, array $api_data): void
private static function showStateSection(array $api_data): void
{
$status = $api_data['client_status'];
$settings = $api_data['client_settings'];
@@ -408,9 +504,9 @@ class AssetTab extends CommonDBTM
}
$internetMode = self::extractSettingValue($settings['internet_mode_enabled'] ?? $settings['internet_mode'] ?? null, 0);
$internetModeDisplay = ((int) $internetMode === 1)
? '<span class="badge bg-success">' . __('Yes', 'urbackup') . '</span>'
: '<span class="badge bg-secondary">' . __('No', 'urbackup') . '</span>';
$internetModeDisplay = ((int) $internetMode === 1)
? '<span class="badge bg-success">' . htmlspecialchars(__('Yes', 'urbackup')) . '</span>'
: '<span class="badge bg-secondary">' . htmlspecialchars(__('No', 'urbackup')) . '</span>';
$rows = [
__('Client version', 'urbackup') => $status['client_version_string'] ?? $status['client_version'] ?? $status['version'] ?? '-',
@@ -420,17 +516,23 @@ class AssetTab extends CommonDBTM
__('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 "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars((string) $label) . "</td>";
echo "<td>" . $displayValue . "</td>";
echo "<td>" . htmlspecialchars((string) $displayValue) . "</td>";
echo "</tr>";
}
// Internet mode is rendered as a badge (intentional HTML), so it must
// not go through the generic escaped loop above.
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Internet mode', 'urbackup')) . "</td>";
echo "<td>" . $internetModeDisplay . "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Internet authentication key', 'urbackup')) . "</td>";
echo "<td>";
@@ -455,16 +557,12 @@ class AssetTab extends CommonDBTM
* Show actions section.
*
* @param CommonDBTM $item Asset
* @param Server $server Server
* @param array<string, mixed> $link Link
* @param array<string, mixed> $api_data API data
*
* @return void
*/
private static function showActionsSection(
CommonDBTM $item,
Server $server,
array $link,
array $api_data
): void {
echo "<table class='tab_cadre_fixe'>";
+169 -33
View File
@@ -11,9 +11,10 @@ declare(strict_types=1);
namespace GlpiPlugin\Urbackup;
use CommonDBTM;
use Dropdown;
use Glpi\Asset\Asset;
use Glpi\Asset\AssetDefinitionManager;
use DBmysql;
use GlpiPlugin\Urbackup\Capacity\UrBackupCapacity;
use Html;
use Session;
@@ -21,6 +22,16 @@ class Config extends CommonDBTM
{
public static $rightname = 'config';
/**
* Config key storing whether the UrBackup tab is enabled on Computer.
*/
private const CONFIG_ENABLE_COMPUTER = 'enable_computer';
/**
* In-memory cache of the `enable_computer` config value.
*/
private static ?bool $enable_computer_cache = null;
/**
* Get type name.
*
@@ -45,17 +56,6 @@ class Config extends CommonDBTM
return 'glpi_plugin_urbackup_configs';
}
/**
* Ensure default configuration.
*
* @return void
*/
public static function ensureDefaultConfiguration(): void
{
// Computer is always enabled by default.
// Asset Definition types are managed via the native Capacities UI.
}
/**
* Get assettypes table name.
*
@@ -66,6 +66,47 @@ class Config extends CommonDBTM
return 'glpi_plugin_urbackup_assettypes';
}
/**
* Whether the UrBackup tab is enabled on Computer items.
*
* Stored in glpi_plugin_urbackup_configs under the `enable_computer` key.
* Enabled by default; falls back to true when the table does not exist yet
* (e.g. during plugin install) or when the row is missing (backward
* compatibility with versions where Computer was always enabled).
*
* @return bool
*/
public static function getEnableComputer(): bool
{
if (self::$enable_computer_cache !== null) {
return self::$enable_computer_cache;
}
global $DB;
$table = static::getTable();
if (!$DB->tableExists($table)) {
self::$enable_computer_cache = true;
return self::$enable_computer_cache;
}
try {
$iterator = $DB->request([
'FROM' => $table,
'WHERE' => ['name' => self::CONFIG_ENABLE_COMPUTER],
'LIMIT' => 1,
]);
$row = $iterator->current();
self::$enable_computer_cache = $row === false || (string) ($row['value'] ?? '') === '1';
} catch (\Throwable) {
self::$enable_computer_cache = true;
}
return self::$enable_computer_cache;
}
/**
* Check whether itemtype is enabled for UrBackup.
*
@@ -80,7 +121,7 @@ class Config extends CommonDBTM
}
if ($itemtype === 'Computer') {
return true;
return self::getEnableComputer();
}
// All Asset subclasses are enabled by default.
@@ -101,7 +142,11 @@ class Config extends CommonDBTM
*/
public static function getEnabledItemtypes(): array
{
$itemtypes = ['Computer'];
$itemtypes = [];
if (self::getEnableComputer()) {
$itemtypes[] = 'Computer';
}
if (class_exists(AssetDefinitionManager::class)) {
$manager = AssetDefinitionManager::getInstance();
@@ -116,6 +161,42 @@ class Config extends CommonDBTM
return $itemtypes;
}
/**
* Get the Asset Definitions that have the UrBackup capacity enabled.
*
* @return array<int, \Glpi\Asset\AssetDefinition>
*/
public static function getEnabledAssetDefinitions(): array
{
if (!class_exists(AssetDefinitionManager::class)) {
return [];
}
$manager = AssetDefinitionManager::getInstance();
$capacities = $manager->getAvailableCapacities();
$urbackup_capacity = $capacities[UrBackupCapacity::class] ?? null;
$definitions = [];
foreach ($manager->getDefinitions() as $definition) {
if ($urbackup_capacity !== null) {
if ($definition->hasCapacityEnabled($urbackup_capacity)) {
$definitions[] = $definition;
}
} else {
// Fallback: decode the raw capacities JSON.
$decoded = json_decode((string) ($definition->fields['capacities'] ?? '[]'), true);
if (
is_array($decoded)
&& in_array(UrBackupCapacity::class, array_column($decoded, 'name'), true)
) {
$definitions[] = $definition;
}
}
}
return $definitions;
}
/**
* Show config form.
*
@@ -128,38 +209,93 @@ class Config extends CommonDBTM
{
Session::checkRight('config', UPDATE);
$enable_computer = self::getEnableComputer();
echo "<div class='center'>";
echo "<form method='post' action='" . static::getFormURL() . "'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr><th colspan='2'>" . htmlspecialchars(__('UrBackup configuration', 'urbackup')) . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<td><strong>" . htmlspecialchars(__('Computer', 'urbackup')) . "</strong></td>";
echo "<td><span class='badge bg-success'>" . htmlspecialchars(__('Always enabled', 'urbackup')) . "</span></td>";
echo "<td>";
echo "<strong>" . htmlspecialchars(__('Enable UrBackup on Computer', 'urbackup')) . "</strong><br>";
echo "<span class='text-muted'>" . htmlspecialchars(
__(
'Show the UrBackup tab on Computer items. When disabled, Computer is ignored everywhere (tabs, massive actions, links).',
'urbackup'
)
) . "</span>";
echo "</td>";
echo "<td>";
Dropdown::showYesNo(self::CONFIG_ENABLE_COMPUTER, (int) $enable_computer);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_2'>";
echo "<td class='center' colspan='2'>";
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
echo Html::submit(__('Save'), ['name' => 'update', 'class' => 'btn btn-primary']);
echo "</td>";
echo "</tr>";
echo "</table>";
echo "</form>";
echo "<br>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr><th colspan='3'>";
echo htmlspecialchars(__('Custom assets with "Urbackup" capacity enabled', 'urbackup'));
echo "</th></tr>";
$definitions = self::getEnabledAssetDefinitions();
if (count($definitions) === 0) {
echo "<tr class='tab_bg_1'>";
echo "<td class='center' colspan='3'>";
echo "<span class='alert alert-info'>";
echo htmlspecialchars(
__('No custom asset with the "Urbackup" capacity enabled.', 'urbackup')
);
echo "</span>";
echo "</td>";
echo "</tr>";
} else {
echo "<tr class='tab_bg_1'>";
echo "<th>" . htmlspecialchars(__('Name')) . "</th>";
echo "<th>" . htmlspecialchars(__('System name')) . "</th>";
echo "<th>" . htmlspecialchars(__('Active')) . "</th>";
echo "</tr>";
foreach ($definitions as $definition) {
$is_active = (bool) ($definition->fields['is_active'] ?? 0);
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars((string) ($definition->fields['label'] ?? '')) . "</td>";
echo "<td>" . htmlspecialchars((string) ($definition->fields['system_name'] ?? '')) . "</td>";
echo "<td>";
if ($is_active) {
echo "<span class='badge bg-success'>" . htmlspecialchars(__('Yes')) . "</span>";
} else {
echo "<span class='badge bg-secondary'>" . htmlspecialchars(__('No')) . "</span>";
}
echo "</td>";
echo "</tr>";
}
}
echo "</table>";
echo "<br>";
echo "<div class='alert alert-info'>";
echo htmlspecialchars(
__('For Asset Definition types, enable/disable UrBackup via Config > Asset definitions > Capacities.', 'urbackup')
__(
'For Asset Definition types, enable/disable UrBackup via Config > Asset definitions > Capacities.',
'urbackup'
)
);
echo "</div>";
echo "</div>";
return true;
}
/**
* Save configuration.
*
* In the new capacity system, asset types are managed via the native Capacities UI.
* This method is kept for backward compatibility but does nothing.
*
* @param array<string, mixed> $input Input data
*
* @return void
*/
public static function saveConfiguration(array $input): void
{
Session::checkRight('config', UPDATE);
}
}
}
+2 -5
View File
@@ -12,7 +12,6 @@ namespace GlpiPlugin\Urbackup;
use CommonDBTM;
use Html;
use Session;
class MassiveAction extends CommonDBTM
{
@@ -216,8 +215,7 @@ class MassiveAction extends CommonDBTM
$result ? \MassiveAction::ACTION_OK : \MassiveAction::ACTION_KO
);
}
}
}
/**
* Process disconnect-from-server massive action.
@@ -261,6 +259,5 @@ class MassiveAction extends CommonDBTM
$result ? \MassiveAction::ACTION_OK : \MassiveAction::ACTION_KO
);
}
}
}
}
+239 -132
View File
@@ -11,9 +11,9 @@ declare(strict_types=1);
namespace GlpiPlugin\Urbackup;
use CommonDBTM;
use CommonGLPI;
use Dropdown;
use Entity;
use GLPIKey;
use Group;
use Html;
use Location;
@@ -465,9 +465,10 @@ class Server extends CommonDBTM
echo "<td>" . htmlspecialchars(__('API password', 'urbackup')) . "</td>";
echo "<td>";
if ($canEdit) {
echo "<input type='password' name='api_password' value='" .
htmlspecialchars((string) ($this->fields['api_password'] ?? '')) .
"' autocomplete='new-password'>";
echo "<input type='password' name='api_password' value='' placeholder='******' autocomplete='new-password'>";
echo "<br><small class='text-muted'>" .
htmlspecialchars(__('Leave empty to keep the current password.', 'urbackup')) .
"</small>";
} else {
echo '******';
}
@@ -483,6 +484,53 @@ class Server extends CommonDBTM
echo "</td>";
echo "</tr>";
$hostItemtype = (string) ($this->fields['host_itemtype'] ?? '');
$hostItemsId = (int) ($this->fields['host_items_id'] ?? 0);
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Hardware host', 'urbackup')) . "</td>";
echo "<td colspan='3'>";
$rand = Dropdown::showItemTypes(
'host_itemtype',
Config::getEnabledItemtypes(),
[
'value' => $hostItemtype,
]
);
echo Html::scriptBlock(
"$(document).on('change', '#dropdown_host_itemtype$rand', function () {"
. "var itemtype = this.value;"
. "if (!itemtype) { $('#urbackup_host_items$rand').html(''); return; }"
. "$.get('" . PLUGIN_URBACKUP_WEB_DIR . "/front/dropdown_host.ajax.php',"
. " { itemtype: itemtype, value: 0 },"
. " function (html) { $('#urbackup_host_items$rand').html(html); }"
. ");"
. "});"
);
echo "<div id='urbackup_host_items$rand' class='mt-2'>";
if ($hostItemtype !== '' && $hostItemsId > 0 && class_exists($hostItemtype)) {
Dropdown::show($hostItemtype, [
'name' => 'host_items_id',
'value' => $hostItemsId,
'entity' => Session::getActiveEntities(),
'display_emptychoice' => true,
]);
}
echo "</div>";
$hostAsset = $this->getHostAsset();
if ($hostAsset !== null) {
echo "<div class='mt-2'><a href='" . htmlspecialchars($hostAsset::getFormURLWithID((int) $hostAsset->fields['id'])) . "'>" .
htmlspecialchars((string) $hostAsset->getName()) .
"</a></div>";
}
echo "</td>";
echo "</tr>";
if ($ID > 0) {
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('UrBackup web interface', 'urbackup')) . "</td>";
@@ -536,6 +584,102 @@ class Server extends CommonDBTM
return sprintf('%s://%s:%d', $protocol, $ip, $port);
}
/**
* Get the asset hosting this UrBackup server.
*
* @return CommonDBTM|null The hosting asset, or null when not set/unresolvable
*/
public function getHostAsset(): ?CommonDBTM
{
$hostItemtype = (string) ($this->fields['host_itemtype'] ?? '');
$hostItemsId = (int) ($this->fields['host_items_id'] ?? 0);
if ($hostItemtype === '' || $hostItemsId <= 0 || !class_exists($hostItemtype)) {
return null;
}
$hostItem = getItemForItemtype($hostItemtype);
if (!$hostItem instanceof CommonDBTM || !$hostItem->getFromDB($hostItemsId)) {
return null;
}
return $hostItem;
}
/**
* Get the UrBackup servers hosted on a given asset.
*
* @param string $itemtype Asset itemtype
* @param int $items_id Asset ID
*
* @return array<int, array<string, mixed>> List of servers hosting the asset
*/
public static function getServersHostingAsset(string $itemtype, int $items_id): array
{
global $DB;
if ($itemtype === '' || $items_id <= 0) {
return [];
}
if (!$DB->fieldExists(self::getTable(), 'host_itemtype')) {
return [];
}
$servers = [];
$iterator = $DB->request([
'FROM' => self::getTable(),
'WHERE' => [
'host_itemtype' => $itemtype,
'host_items_id' => $items_id,
],
'ORDER' => 'name',
]);
foreach ($iterator as $row) {
$servers[] = $row;
}
return $servers;
}
/**
* Get the decrypted API password.
*
* Values encrypted with GLPIKey are decrypted on the fly. Plaintext
* values stored by plugin versions older than 0.7.1 are returned as-is.
*
* @return string
*/
public function getApiPassword(): string
{
$value = (string) ($this->fields['api_password'] ?? '');
if ($value === '' || !self::isApiPasswordEncrypted($value)) {
return $value;
}
return (string) (new GLPIKey())->decrypt($value);
}
/**
* Check whether a stored API password uses the GLPIKey encrypted format.
*
* @param string $value Stored value
*
* @return bool
*/
public static function isApiPasswordEncrypted(string $value): bool
{
$decoded = base64_decode($value, true);
if ($decoded === false) {
return false;
}
return strlen($decoded) >= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES;
}
/**
* Get active servers assigned to a root location.
*
@@ -585,8 +729,10 @@ class Server extends CommonDBTM
if (!isset($input['port']) || $input['port'] === '' || $input['port'] === null) {
$input['port'] = 55414;
} else {
$input['port'] = max(1, min(65535, (int) $input['port']));
}
if (!isset($input['protocol']) || $input['protocol'] === '') {
if (!isset($input['protocol']) || !in_array($input['protocol'], ['http', 'https'], true)) {
$input['protocol'] = 'http';
}
if (!isset($input['is_active']) || $input['is_active'] === '' || $input['is_active'] === null) {
@@ -608,16 +754,20 @@ class Server extends CommonDBTM
return $input;
}
if (isset($input['port']) && ($input['port'] === '' || $input['port'] === null)) {
$input['port'] = 55414;
if (isset($input['port'])) {
$input['port'] = ($input['port'] === '' || $input['port'] === null)
? 55414
: max(1, min(65535, (int) $input['port']));
}
if (isset($input['protocol']) && $input['protocol'] === '') {
if (isset($input['protocol']) && !in_array($input['protocol'], ['http', 'https'], true)) {
$input['protocol'] = 'http';
}
if (isset($input['ignore_ssl']) && ($input['ignore_ssl'] === '' || $input['ignore_ssl'] === null)) {
$input['ignore_ssl'] = 0;
}
$hasNewPassword = isset($input['api_password']) && $input['api_password'] !== '';
if (!empty($input['id']) && (int) $input['id'] > 0) {
$server = new self();
if ($server->getFromDB((int) $input['id'])) {
@@ -626,19 +776,33 @@ class Server extends CommonDBTM
$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'] ?? '';
$apiPassword = $hasNewPassword
? (string) $input['api_password']
: $server->getApiPassword();
$ignoreSsl = $input['ignore_ssl'] ?? $serverFields['ignore_ssl'] ?? 0;
if ($ip !== '') {
// Only test the API connection when connection parameters actually changed,
// otherwise every save would trigger a request (up to 30s when unreachable).
$connectionChanged = $hasNewPassword;
foreach (['ip_address', 'port', 'protocol', 'api_username', 'ignore_ssl'] as $field) {
$newValue = (string) ($input[$field] ?? $serverFields[$field] ?? '');
$oldValue = (string) ($serverFields[$field] ?? '');
if ($newValue !== $oldValue) {
$connectionChanged = true;
break;
}
}
if ($connectionChanged && $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,
'id' => (int) $input['id'],
'ip_address' => $ip,
'port' => $port,
'protocol' => $protocol,
'api_username' => $apiUsername,
'api_password' => $apiPassword,
'ignore_ssl' => $ignoreSsl,
];
try {
@@ -656,97 +820,38 @@ class Server extends CommonDBTM
}
}
if (isset($input['api_password'])) {
if ($input['api_password'] === '') {
unset($input['api_password']);
} else {
$input['api_password'] = (new GLPIKey())->encrypt((string) $input['api_password']);
}
}
if (array_key_exists('host_itemtype', $input) || array_key_exists('host_items_id', $input)) {
$hostItemtype = (string) ($input['host_itemtype'] ?? '');
$hostItemsId = (int) ($input['host_items_id'] ?? 0);
if (
$hostItemtype === ''
|| $hostItemsId <= 0
|| !class_exists($hostItemtype)
|| !Config::isItemtypeEnabled($hostItemtype)
) {
$input['host_itemtype'] = '';
$input['host_items_id'] = 0;
} else {
$hostItem = new $hostItemtype();
if (!$hostItem instanceof CommonDBTM || !$hostItem->getFromDB($hostItemsId)) {
$input['host_itemtype'] = '';
$input['host_items_id'] = 0;
}
}
}
return $input;
}
public function testApiConnection(): array
{
$ip = (string) ($this->fields['ip_address'] ?? '');
if ($ip === '') {
return [
'status' => 'no_ip',
'html' => '<span class="text-muted">' . htmlspecialchars(__('No IP address configured', 'urbackup')) . '</span>',
];
}
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' => '<span class="text-success fw-bold"><i class="ti ti-check"></i> ' .
htmlspecialchars(__('API connection OK', 'urbackup')) . '</span>',
];
}
return [
'status' => 'failed',
'html' => '<span class="text-danger fw-bold"><i class="ti ti-x"></i> ' .
htmlspecialchars(__('API connection failed', 'urbackup')) . '</span><br>' .
'<small class="text-muted">' . htmlspecialchars($result['message'] ?? '') . '</small>',
];
} catch (\Throwable $e) {
$message = $e->getMessage();
$isUnreachable = $this->isNetworkError($message);
return [
'status' => $isUnreachable ? 'unreachable' : 'failed',
'html' => '<span class="' . ($isUnreachable ? 'text-warning' : 'text-danger') . ' fw-bold">' .
'<i class="ti ' . ($isUnreachable ? 'ti-wifi-off' : 'ti-x') . '"></i> ' .
htmlspecialchars($isUnreachable ? __('Server unreachable', 'urbackup') : __('API connection failed', 'urbackup')) .
'</span><br>' .
'<small class="text-muted">' . htmlspecialchars($message) . '</small>',
];
}
}
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) {
@@ -943,10 +1048,36 @@ class Server extends CommonDBTM
'FROM' => 'glpi_plugin_urbackup_serverassets',
]);
$linkedNames = [];
// Batch-load asset names (one query per itemtype) to avoid N+1 lookups.
$assetNames = [];
$idsByItemtype = [];
foreach ($iterator as $row) {
$assetName = ServerAsset::getAssetName($row['itemtype'], (int) $row['items_id']);
if ($assetName !== '') {
$itemtype = (string) $row['itemtype'];
$itemsId = (int) $row['items_id'];
$assetNames[$itemtype . '-' . $itemsId] = null;
$idsByItemtype[$itemtype][] = $itemsId;
}
foreach ($idsByItemtype as $itemtype => $ids) {
if (!class_exists($itemtype)) {
continue;
}
$item = new $itemtype();
if (!$item instanceof CommonDBTM || !$DB->tableExists($item->getTable())) {
continue;
}
$assetIterator = $DB->request([
'FROM' => $item->getTable(),
'WHERE' => ['id' => $ids],
]);
foreach ($assetIterator as $assetRow) {
$assetNames[$itemtype . '-' . (int) $assetRow['id']] = (string) $assetRow['name'];
}
}
$linkedNames = [];
foreach ($assetNames as $assetName) {
if ($assetName !== null && $assetName !== '') {
$linkedNames[] = strtolower($assetName);
}
}
@@ -1456,30 +1587,6 @@ JAVASCRIPT;
return $groups;
}
private static function getAssetGroupName(string $itemtype, int $items_id, array &$cache): string
{
global $DB;
$iterator = $DB->request([
'FROM' => 'glpi_groups_items',
'WHERE' => [
'itemtype' => $itemtype,
'items_id' => $items_id,
'type' => \Group_Item::GROUP_TYPE_NORMAL,
],
'LIMIT' => 1,
]);
foreach ($iterator as $row) {
$groupId = (int) ($row['groups_id'] ?? 0);
if ($groupId > 0) {
return self::getCachedName('Group', $groupId, $cache);
}
}
return '';
}
private static function getCachedLocationName(int $id, array &$cache): string
{
if ($id <= 0) {
+3 -40
View File
@@ -17,8 +17,6 @@ use RuntimeException;
class UrbackupApiClient
{
private object $server;
private string $base_url;
private string $username;
@@ -52,10 +50,11 @@ class UrbackupApiClient
*/
public function __construct(object $server)
{
$this->server = $server;
$this->base_url = rtrim($server->getWebInterfaceUrl(), '/') . '/x';
$this->username = (string) ($server->fields['api_username'] ?? '');
$this->password = (string) ($server->fields['api_password'] ?? '');
$this->password = $server instanceof Server
? $server->getApiPassword()
: (string) ($server->fields['api_password'] ?? '');
$this->ignore_ssl = ((int) ($server->fields['ignore_ssl'] ?? 0)) === 1;
$this->server_version = (string) ($server->fields['server_version'] ?? '');
$this->is_version_2_4_or_higher = $this->detectVersion2_4OrHigher();
@@ -95,23 +94,6 @@ class UrbackupApiClient
return $this->is_version_2_4_or_higher ? 'internet_mode_enabled' : 'internet_mode';
}
/**
* Extract setting value from 2.5+ structured format or simple value.
*
* @param mixed $setting Setting value
* @param mixed $default Default value
*
* @return mixed
*/
public function extractSettingValue(mixed $setting, mixed $default = null): mixed
{
if (is_array($setting) && array_key_exists('value', $setting)) {
return $setting['value'];
}
return $setting ?? $default;
}
/**
* Test API connection.
*
@@ -361,25 +343,6 @@ class UrbackupApiClient
unset($this->cached_settings[$client_id]);
}
public function changeClientSetting(string $client_name, string $key, mixed $value): bool
{
$client_id = $this->getClientIdByName($client_name);
if ($client_id <= 0) {
return false;
}
$data = $this->apiAction('settings', [
'sa' => 'clientsettings_save',
't_clientid' => $client_id,
'overwrite' => 'true',
$key => (string) $value,
]);
$this->clearSettingsCache($client_id);
return $this->responseIsSuccess($data);
}
public function updateClientSettings(string $client_name, string $key, string $value): bool
{
$client_id = $this->getClientIdByName($client_name);