connessione con hardware in assets
This commit is contained in:
+131
-33
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user