1 Commits

Author SHA1 Message Date
mariano 2c1676dd47 test 2026-05-20 13:05:55 +02:00
23 changed files with 151 additions and 872 deletions
-53
View File
@@ -267,56 +267,3 @@ Il test del pulsante "Test API" continua a restituire 403 Forbidden quando acces
- `src/AssetTab.php`: Aggiunti metodi `startBackup()`, `saveInternetMode()`, `saveDefaultDirs()`
- `src/UrbackupApiClient.php`: Aggiunti metodi `updateClientSettings()`, `saveInternetMode()`
- `front/asset.form.php`: Gestione azioni per salvataggio impostazioni e backup
### Refactoring server.form.php e nuove tab (v0.4.7)
**Data**: 2026-05-21
**Descrizione**: Restrutturazione completa della pagina server con tab laterali e nuove funzionalità:
**Refactoring Server::showForm()**:
- Estratto `showFormFields(?int $ID)` da `showForm()` per permettere layout custom
- `showForm()` ora chiama header + fields + buttons in sequenza
**Tab laterali (server.form.php)**:
- Side tabs a sinistra (col-2) con `nav-pills flex-column`
- 4 tab: Server, Linked clients, Unlinked clients, Missing clients
- Tab persistence via URL hash (JS su `shown.bs.tab`)
**Missing clients tab**:
- Mostra asset GLPI con root location matching, non ancora collegati a ServerAsset e non presenti su UrBackup
- Colonne: Name (link all'asset), Entity, Location (completename), Inventory number, IP, State, User, Group
- Ricerca testuale client-side
- Ordinamento colonne click-to-sort vanilla JS
- Navigazione server spostata in server.form.php (sopra i tab, indipendente dalle sezioni)
**Bug fix namespace risolto**:
- `getCachedName()` usava `new $classname()` con stringa → PHP cercava `GlpiPlugin\Urbackup\Group` invece di `\Group`
- Fix: `$fqcn = '\\' . ltrim($classname, '\\')` per forzare global namespace
**Gruppo asset corretto**:
- Gruppo non in `glpi_computers.groups_id` (colonna inesistente)
- Letto da `glpi_groups_items` WHERE `type = 1` (GROUP_TYPE_NORMAL)
- Usa `completename` per gerarchia (es. "Helpdesk > Livello 1")
**Indirizzo IP asset**:
- Query JOIN: `glpi_ipaddresses → glpi_networknames → glpi_networkports`
- Primo IP valido per l'item
**Breadcrumb**:
- Cambiato da `'Assets'` a `'admin'` in `Html::header()` di server.php e server.form.php
- Mostra: Pagina principale > Amministrazione > UrBackup servers
**Fix port default per nuovi server**:
- `prepareInputForAdd()` ora setta default 55414 per `port`, 'http' per `protocol`, 1 per `is_active`, ecc.
- `prepareInputForUpdate()` normalizza anche valori vuoti
**File modificati**:
- `src/Server.php`: `showFormFields()`, `showMissingClientsTab()`, `getCachedName()` fix, `getAssetGroupName()`, `getAssetIp()`, `getCachedLocationName()`, `prepareInputForAdd/Update` default fix, icona `ti-cloud-up`
- `front/server.form.php`: Tab laterali, tab hash JS, breadcrumb admin, navigazione server
- `front/server.php`: Breadcrumb admin, rimosso pulsante "Add" manuale
- `src/Profile.php`: Icona `ti-cloud-up`
- `src/AssetTab.php`: Icona `ti-cloud-up`
- **Permessi**: `front/server.form.php` ora usa READ invece di UPDATE per accesso form; View search option + pulsanti Connect nascosti per READ; API username/password nascosti per READ
- **i18n**: aggiunte 17 stringhe mancanti con dominio `urbackup` a tutti i file e .po/.mo (it, en, de)
- **Bug fix**: ServerAsset colonne rimosse, asset.form.php usa disconnectAsset(), `declare(strict_types=1)` in 11 file
-23
View File
@@ -1,23 +0,0 @@
{
"name": "finstral/glpi-urbackup-plugin",
"description": "UrBackup integration plugin for GLPI 11",
"type": "glpi-plugin",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.3"
},
"autoload": {
"psr-4": {
"GlpiPlugin\\Urbackup\\": "src/"
}
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"extra": {
"glpi-plugin": {
"key": "urbackup"
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "glpi/urbackup-plugin",
"name": "finstral/glpi-urbackup-plugin",
"description": "UrBackup integration plugin for GLPI 11",
"type": "glpi-plugin",
"license": "GPL-2.0-or-later",
+15 -9
View File
@@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
/**
* -------------------------------------------------------------------------
* UrBackup plugin for GLPI
@@ -27,11 +25,11 @@ $itemtype = (string) ($_POST['itemtype'] ?? $_GET['itemtype'] ?? '');
$items_id = (int) ($_POST['items_id'] ?? $_GET['items_id'] ?? 0);
if ($itemtype === '' || $items_id <= 0) {
Html::displayValidationError(__('Invalid parameters', 'urbackup'));
Html::displayValidationError(__('Invalid parameters'));
}
if (!in_array($itemtype, Config::getEnabledItemtypes(), true)) {
Html::displayValidationError(__('Item type not enabled for UrBackup', 'urbackup'));
Html::displayValidationError(__('Item type not enabled for UrBackup'));
}
$item = getItemForItemtype($itemtype);
@@ -48,13 +46,13 @@ if (isset($_POST['connect'])) {
$server_id = (int) ($_POST['plugin_urbackup_servers_id'] ?? 0);
if ($server_id <= 0) {
Html::displayValidationError(__('No server selected', 'urbackup'));
Html::displayValidationError(__('No server selected'));
}
$link = ServerAsset::getLinkForAsset($itemtype, $items_id);
if ($link !== null) {
Html::displayValidationError(__('Asset is already linked to a server', 'urbackup'));
Html::displayValidationError(__('Asset is already linked to a server'));
}
$result = ServerAsset::createForAsset($itemtype, $items_id, $server_id);
@@ -63,7 +61,7 @@ if (isset($_POST['connect'])) {
$item->getFromDB($items_id);
Html::redirect($item->getLinkURL());
} else {
Html::displayValidationError(__('Failed to link asset to server', 'urbackup'));
Html::displayValidationError(__('Failed to link asset to server'));
}
}
@@ -72,13 +70,21 @@ if (isset($_POST['disconnect'])) {
Html::displayRightError();
}
$result = ServerAsset::disconnectAsset($itemtype, $items_id);
global $DB;
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, false);
if ($link === null) {
Html::displayValidationError(__('Asset is not linked to any server'));
}
$result = $DB->delete('glpi_plugin_urbackup_serverassets', ['id' => (int) $link['id']]);
if ($result) {
$item->getFromDB($items_id);
Html::redirect($item->getLinkURL());
} else {
Html::displayValidationError(__('Failed to disconnect asset from server', 'urbackup'));
Html::displayValidationError(__('Failed to disconnect asset from server'));
}
}
-2
View File
@@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
use GlpiPlugin\Urbackup\Config;
use Html;
+23 -114
View File
@@ -1,10 +1,9 @@
<?php
declare(strict_types=1);
use GlpiPlugin\Urbackup\Profile;
use GlpiPlugin\Urbackup\Server;
use GlpiPlugin\Urbackup\ServerAsset;
use Html;
if (!defined('GLPI_ROOT')) {
define('GLPI_ROOT', dirname(__DIR__, 4));
@@ -12,7 +11,7 @@ if (!defined('GLPI_ROOT')) {
include_once GLPI_ROOT . "/inc/includes.php";
if (!Profile::canCurrentUser(READ)) {
if (!Profile::canCurrentUser(UPDATE)) {
Html::displayRightError();
}
@@ -49,120 +48,30 @@ $ID = $_GET['id'] ?? null;
Html::header(
$ID ? __('Edit UrBackup server', 'urbackup') : __('Add UrBackup server', 'urbackup'),
'',
'admin',
'Assets',
'GlpiPlugin\Urbackup\Server'
);
if ($ID > 0) {
global $DB;
$server->showForm($ID);
$server->getFromDB($ID);
$serverIterator = $DB->request([
'FROM' => Server::getTable(),
'ORDER' => 'name',
]);
$serverIds = [];
foreach ($serverIterator as $row) {
$serverIds[(int) $row['id']] = (string) $row['name'];
}
$serverIdKeys = array_keys($serverIds);
$currentIndex = array_search((int) $ID, $serverIdKeys, true);
$totalServers = count($serverIds);
$prevId = ($currentIndex !== false && $currentIndex > 0) ? $serverIdKeys[$currentIndex - 1] : null;
$nextId = ($currentIndex !== false && $currentIndex < $totalServers - 1) ? $serverIdKeys[$currentIndex + 1] : null;
$baseUrl = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php';
?>
<div class="d-flex justify-content-between align-items-center mb-2">
<div class="d-flex align-items-center">
<a href="<?php echo htmlspecialchars(PLUGIN_URBACKUP_WEB_DIR . '/front/server.php'); ?>" class="btn btn-sm btn-icon btn-ghost-secondary me-2"
data-bs-toggle="tooltip" data-bs-placement="bottom" title="<?php echo htmlspecialchars(__('List')); ?>">
<i class="ti ti-list-search fs-2"></i>
</a>
<?php if ($prevId !== null): ?>
<a href="<?php echo htmlspecialchars($baseUrl . '?id=' . $prevId); ?>"
class="btn btn-sm btn-icon btn-ghost-secondary me-2"
data-bs-toggle="tooltip" data-bs-placement="bottom" title="<?php echo htmlspecialchars(__('Previous')); ?>">
<i class="fs-2 ti ti-chevron-left"></i>
</a>
<?php else: ?>
<span class="btn btn-sm btn-icon btn-ghost-secondary me-2 bs-invisible">
<i class="fs-2 ti ti-chevron-left"></i>
</span>
<?php endif; ?>
<span class="fw-bold mx-2"><?php echo htmlspecialchars(($currentIndex !== false ? $currentIndex + 1 : 1) . ' / ' . $totalServers); ?></span>
<?php if ($nextId !== null): ?>
<a href="<?php echo htmlspecialchars($baseUrl . '?id=' . $nextId); ?>"
class="btn btn-sm btn-icon btn-ghost-secondary ms-2"
data-bs-toggle="tooltip" data-bs-placement="bottom" title="<?php echo htmlspecialchars(__('Next')); ?>">
<i class="fs-2 ti ti-chevron-right"></i>
</a>
<?php else: ?>
<span class="btn btn-sm btn-icon btn-ghost-secondary ms-2 bs-invisible">
<i class="fs-2 ti ti-chevron-right"></i>
</span>
<?php endif; ?>
</div>
</div>
<div class="row">
<div class="col-2">
<ul class="nav nav-pills flex-column">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#tab-server">
<?php echo htmlspecialchars(__('Server')); ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-linked">
<?php echo htmlspecialchars(__('Linked clients', 'urbackup')); ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-unlinked">
<?php echo htmlspecialchars(__('Unlinked clients', 'urbackup')); ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-missing">
<?php echo htmlspecialchars(__('Missing clients', 'urbackup')); ?>
</a>
</li>
</ul>
</div>
<div class="col-10">
<div class="tab-content">
<div class="tab-pane active" id="tab-server">
<?php $server->showForm($ID); ?>
</div>
<div class="tab-pane" id="tab-linked">
<?php Server::showLinkedClientsTab($server); ?>
</div>
<div class="tab-pane" id="tab-unlinked">
<?php Server::showUnlinkedClientsTab($server); ?>
</div>
<div class="tab-pane" id="tab-missing">
<?php Server::showMissingClientsTab($server); ?>
</div>
</div>
</div>
</div>
<?php
} else {
$server->showForm($ID);
if ($ID > 0 && $server->getFromDB($ID)) {
echo '<div class="card mt-4">';
echo '<div class="card-header">';
echo '<h5 class="mb-0">' . htmlspecialchars(__('Linked clients', 'urbackup')) . '</h5>';
echo '</div>';
echo '<div class="card-body">';
Server::showLinkedClientsTab($server);
echo '</div>';
echo '</div>';
echo '<div class="card mt-4">';
echo '<div class="card-header">';
echo '<h5 class="mb-0">' . htmlspecialchars(__('Unlinked clients', 'urbackup')) . '</h5>';
echo '</div>';
echo '<div class="card-body">';
Server::showUnlinkedClientsTab($server);
echo '</div>';
echo '</div>';
}
?>
<script>
$(function () {
var hash = window.location.hash;
if (hash) {
$('[data-bs-toggle="tab"][href="' + hash + '"]').tab('show');
}
$('[data-bs-toggle="tab"]').on('shown.bs.tab', function (e) {
window.location.hash = $(this).attr('href');
});
});
</script>
<?php
Html::footer();
Html::footer();
+18 -6
View File
@@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
use GlpiPlugin\Urbackup\Profile;
use GlpiPlugin\Urbackup\Server;
use Html;
@@ -17,13 +15,27 @@ if (!Profile::canCurrentUser(READ)) {
Html::displayRightError();
}
$can_read = Profile::canCurrentUser(READ);
$can_create = Profile::canCurrentUser(CREATE);
Html::header(
__('UrBackup Servers', 'urbackup'),
'UrBackup Servers',
'',
'admin',
'GlpiPlugin\Urbackup\Server'
'Assets',
''
);
if ($can_create) {
echo "<div class='center'>";
echo Html::link(
__('Add UrBackup server', 'urbackup'),
'/plugins/urbackup/front/server.form.php',
['class' => 'btn btn-primary']
);
echo "</div>";
echo "<br>";
}
Search::show('GlpiPlugin\Urbackup\Server', [
'is_deleted' => 0,
'massiveaction' => true,
@@ -33,4 +45,4 @@ Search::show('GlpiPlugin\Urbackup\Server', [
],
]);
Html::footer();
Html::footer();
+3 -6
View File
@@ -1,7 +1,4 @@
<?php
declare(strict_types=1);
/**
* AJAX endpoint for testing UrBackup API
*/
@@ -14,21 +11,21 @@ Html::header_nocache();
Session::checkLoginUser();
if (!Session::haveRight('plugin_urbackup', READ)) {
echo json_encode(['success' => false, 'message' => __('No permission', 'urbackup')]);
echo json_encode(['success' => false, 'message' => 'No permission']);
exit;
}
$server_id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0);
if ($server_id <= 0) {
echo json_encode(['success' => false, 'message' => __('Invalid server ID', 'urbackup')]);
echo json_encode(['success' => false, 'message' => 'Invalid server ID']);
exit;
}
$server = new GlpiPlugin\Urbackup\Server();
if (!$server->getFromDB($server_id)) {
echo json_encode(['success' => false, 'message' => __('Server not found', 'urbackup')]);
echo json_encode(['success' => false, 'message' => 'Server not found']);
exit;
}
+11 -3
View File
@@ -1,18 +1,26 @@
<?php
declare(strict_types=1);
/**
* -------------------------------------------------------------------------
* UrBackup plugin for GLPI
* -------------------------------------------------------------------------
*
* Install/update process for GLPI 11.
*
* GLPI 11 Migration class does not expose createTable()/addTable().
* Compatible GLPI plugin pattern:
*
* - initial schema creation from SQL file using $DB->runFile()
* - schema evolution using Migration addField(), addKey(), executeMigration()
*
* -------------------------------------------------------------------------
*/
use GlpiPlugin\Urbackup\Config;
use GlpiPlugin\Urbackup\Profile;
if (!defined('GLPI_ROOT')) {
die(__('Sorry. You cannot access this file directly.', 'urbackup'));
die('Sorry. You cannot access this file directly.');
}
/**
+4 -3
View File
@@ -1,17 +1,18 @@
<?php
declare(strict_types=1);
/**
* -------------------------------------------------------------------------
* UrBackup plugin for GLPI
* -------------------------------------------------------------------------
*
* Uninstall process for GLPI 11.
* -------------------------------------------------------------------------
*/
use GlpiPlugin\Urbackup\Profile;
if (!defined('GLPI_ROOT')) {
die(__('Sorry. You cannot access this file directly.', 'urbackup'));
die('Sorry. You cannot access this file directly.');
}
/**
BIN
View File
Binary file not shown.
+1 -52
View File
@@ -155,55 +155,4 @@ msgid "Last backup"
msgstr "Letztes Backup"
msgid "Show"
msgstr "Anzeigen"
msgid "HTTP"
msgstr "HTTP"
msgid "HTTPS"
msgstr "HTTPS"
msgid "API Error"
msgstr "API-Fehler"
msgid "Unknown"
msgstr "Unbekannt"
msgid "Asset Definition"
msgstr "Asset-Definition"
msgid "Legacy"
msgstr "Legacy"
msgid "UrBackup Servers"
msgstr "UrBackup-Server"
msgid "Invalid parameters"
msgstr "Ungültige Parameter"
msgid "Item type not enabled for UrBackup"
msgstr "Elementtyp für UrBackup nicht aktiviert"
msgid "No server selected"
msgstr "Kein Server ausgewählt"
msgid "Asset is already linked to a server"
msgstr "Asset ist bereits mit einem Server verknüpft"
msgid "Failed to link asset to server"
msgstr "Verknüpfung von Asset mit Server fehlgeschlagen"
msgid "Failed to disconnect asset from server"
msgstr "Trennung von Asset vom Server fehlgeschlagen"
msgid "No permission"
msgstr "Keine Berechtigung"
msgid "Invalid server ID"
msgstr "Ungültige Server-ID"
msgid "Server not found"
msgstr "Server nicht gefunden"
msgid "Sorry. You cannot access this file directly."
msgstr "Entschuldigung. Sie können nicht direkt auf diese Datei zugreifen."
msgstr "Anzeigen"
BIN
View File
Binary file not shown.
+1 -52
View File
@@ -155,55 +155,4 @@ msgid "Last backup"
msgstr "Last backup"
msgid "Show"
msgstr "Show"
msgid "HTTP"
msgstr "HTTP"
msgid "HTTPS"
msgstr "HTTPS"
msgid "API Error"
msgstr "API Error"
msgid "Unknown"
msgstr "Unknown"
msgid "Asset Definition"
msgstr "Asset Definition"
msgid "Legacy"
msgstr "Legacy"
msgid "UrBackup Servers"
msgstr "UrBackup Servers"
msgid "Invalid parameters"
msgstr "Invalid parameters"
msgid "Item type not enabled for UrBackup"
msgstr "Item type not enabled for UrBackup"
msgid "No server selected"
msgstr "No server selected"
msgid "Asset is already linked to a server"
msgstr "Asset is already linked to a server"
msgid "Failed to link asset to server"
msgstr "Failed to link asset to server"
msgid "Failed to disconnect asset from server"
msgstr "Failed to disconnect asset from server"
msgid "No permission"
msgstr "No permission"
msgid "Invalid server ID"
msgstr "Invalid server ID"
msgid "Server not found"
msgstr "Server not found"
msgid "Sorry. You cannot access this file directly."
msgstr "Sorry. You cannot access this file directly."
msgstr "Show"
BIN
View File
Binary file not shown.
+1 -52
View File
@@ -155,55 +155,4 @@ msgid "Last backup"
msgstr "Ultimo backup"
msgid "Show"
msgstr "Mostra"
msgid "HTTP"
msgstr "HTTP"
msgid "HTTPS"
msgstr "HTTPS"
msgid "API Error"
msgstr "Errore API"
msgid "Unknown"
msgstr "Sconosciuto"
msgid "Asset Definition"
msgstr "Definizione asset"
msgid "Legacy"
msgstr "Legacy"
msgid "UrBackup Servers"
msgstr "Server UrBackup"
msgid "Invalid parameters"
msgstr "Parametri non validi"
msgid "Item type not enabled for UrBackup"
msgstr "Tipo oggetto non abilitato per UrBackup"
msgid "No server selected"
msgstr "Nessun server selezionato"
msgid "Asset is already linked to a server"
msgstr "L'asset è già collegato a un server"
msgid "Failed to link asset to server"
msgstr "Collegamento asset al server fallito"
msgid "Failed to disconnect asset from server"
msgstr "Disconnessione asset dal server fallita"
msgid "No permission"
msgstr "Nessun permesso"
msgid "Invalid server ID"
msgstr "ID server non valido"
msgid "Server not found"
msgstr "Server non trovato"
msgid "Sorry. You cannot access this file directly."
msgstr "Spiacenti. Non puoi accedere direttamente a questo file."
msgstr "Mostra"
+1 -3
View File
@@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
// Force OPcache to reload this file when accessed via web
if (PHP_SAPI !== 'cli' && function_exists('opcache_invalidate')) {
opcache_invalidate(__FILE__, true);
@@ -99,7 +97,7 @@ function plugin_version_urbackup(): array
return [
'name' => __('UrBackup', 'urbackup'),
'version' => PLUGIN_URBACKUP_VERSION,
'author' => 'Mariano Benzi',
'author' => 'Finstral',
'license' => 'GPL-2.0-or-later',
'homepage' => '',
'requirements' => [
+16 -44
View File
@@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
/**
* -------------------------------------------------------------------------
* UrBackup plugin for GLPI
@@ -65,7 +63,7 @@ class AssetTab extends CommonDBTM
return '';
}
return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-up');
return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-upload');
}
/**
@@ -326,42 +324,15 @@ class AssetTab extends CommonDBTM
): void {
echo "<div class='plugin-urbackup-inner-tabs'>";
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
echo '<ul class="nav nav-tabs" id="urbackupTabs">';
echo '<li class="nav-item">';
echo '<a class="nav-link active" id="state-tab" data-bs-toggle="tab" href="#state" role="tab">';
echo htmlspecialchars(__('State', 'urbackup'));
echo '</a></li>';
if ($canWrite) {
echo '<li class="nav-item">';
echo '<a class="nav-link" id="actions-tab" data-bs-toggle="tab" href="#actions" role="tab">';
echo htmlspecialchars(__('Actions', 'urbackup'));
echo '</a></li>';
}
echo '<li class="nav-item">';
echo '<a class="nav-link" id="logs-tab" data-bs-toggle="tab" href="#logs" role="tab">';
echo htmlspecialchars(__('Info / Log', 'urbackup'));
echo '</a></li>';
echo '</ul>';
echo '<div class="tab-content">';
echo '<div class="tab-pane fade show active" id="state" role="tabpanel">';
echo "<h3>" . htmlspecialchars(__('State', 'urbackup')) . "</h3>";
self::showStateSection($server, $link, $api_data);
echo '</div>';
if ($canWrite) {
echo '<div class="tab-pane fade" id="actions" role="tabpanel">';
self::showActionsSection($item, $server, $link, $api_data);
echo '</div>';
}
echo "<h3>" . htmlspecialchars(__('Actions', 'urbackup')) . "</h3>";
self::showActionsSection($item, $server, $link, $api_data);
echo '<div class="tab-pane fade" id="logs" role="tabpanel">';
echo "<h3>" . htmlspecialchars(__('Info / Log', 'urbackup')) . "</h3>";
self::showInfoLogSection($api_data);
echo '</div>';
echo '</div>';
echo "</div>";
}
@@ -456,6 +427,17 @@ class AssetTab extends CommonDBTM
echo "<table class='tab_cadre_fixe'>";
echo "<tr><th colspan='2'>" . htmlspecialchars(__('Available actions', 'urbackup')) . "</th></tr>";
if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) {
echo "<tr class='tab_bg_1'>";
echo "<td colspan='2'>";
echo htmlspecialchars(__('You do not have permission for UrBackup actions.', 'urbackup'));
echo "</td>";
echo "</tr>";
echo "</table>";
return;
}
if (!$api_data['client_found'] && Profile::canCurrentUser(CREATE)) {
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Create client in UrBackup', 'urbackup')) . "</td>";
@@ -483,20 +465,10 @@ class AssetTab extends CommonDBTM
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Backup commands', 'urbackup')) . "</td>";
echo "<td>";
echo "<div style='display:flex;gap:24px'>";
echo "<div>";
echo "<strong>" . htmlspecialchars(__('File backup', 'urbackup')) . "</strong><br>";
self::showActionButton($item, 'incremental_file_backup', __('Incremental file backup', 'urbackup'), 'btn btn-secondary');
echo "<br>";
self::showActionButton($item, 'full_file_backup', __('Full file backup', 'urbackup'), 'btn btn-secondary');
echo "</div>";
echo "<div>";
echo "<strong>" . htmlspecialchars(__('Image backup', 'urbackup')) . "</strong><br>";
self::showActionButton($item, 'incremental_image_backup', __('Incremental image backup', 'urbackup'), 'btn btn-secondary');
echo "<br>";
self::showActionButton($item, 'full_image_backup', __('Full image backup', 'urbackup'), 'btn btn-secondary');
echo "</div>";
echo "</div>";
echo "</td>";
echo "</tr>";
+2 -2
View File
@@ -268,7 +268,7 @@ class Config extends CommonDBTM
'checked' => $is_default,
]);
echo "</td>";
echo "<td><span class='badge bg-info'>" . htmlspecialchars(__('Asset Definition', 'urbackup')) . "</span></td>";
echo "<td><span class='badge bg-info'>Asset Definition</span></td>";
} else {
// Legacy type
echo "<td>";
@@ -278,7 +278,7 @@ class Config extends CommonDBTM
]);
echo "</td>";
echo "<td>" . ($is_default ? htmlspecialchars(__('Yes', 'urbackup')) : '') . "</td>";
echo "<td><span class='badge bg-secondary'>" . htmlspecialchars(__('Legacy', 'urbackup')) . "</span></td>";
echo "<td><span class='badge bg-secondary'>Legacy</span></td>";
}
echo "</tr>";
-2
View File
@@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
/**
* -------------------------------------------------------------------------
* UrBackup plugin for GLPI
+1 -1
View File
@@ -14,7 +14,7 @@ class Profile extends \Profile
public static function getIcon()
{
return 'ti ti-cloud-up';
return 'ti ti-server';
}
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
+39 -438
View File
@@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
/**
* -------------------------------------------------------------------------
* UrBackup plugin for GLPI
@@ -14,12 +12,9 @@ use CommonDBTM;
use CommonGLPI;
use Dropdown;
use Entity;
use Group;
use Html;
use Location;
use Session;
use State;
use User;
class Server extends CommonDBTM
{
@@ -156,7 +151,7 @@ class Server extends CommonDBTM
if (self::canView()) {
$menu['title'] = self::getMenuName();
$menu['page'] = self::getSearchURL(false);
$menu['icon'] = 'ti ti-cloud-up';
$menu['icon'] = 'ti ti-server';
$menu['links']['search'] = self::getSearchURL(false);
@@ -297,17 +292,15 @@ class Server extends CommonDBTM
'datatype' => 'datetime',
];
if (Session::haveRight(self::$rightname, UPDATE)) {
$tab[] = [
'id' => 13,
'table' => self::getTable(),
'field' => 'id',
'name' => __('View', 'urbackup'),
'massiveaction' => false,
'datatype' => 'raw',
'searchtype' => 'view',
];
}
$tab[] = [
'id' => 13,
'table' => self::getTable(),
'field' => 'id',
'name' => __('View', 'urbackup'),
'massiveaction' => false,
'datatype' => 'raw',
'searchtype' => 'view',
];
return $tab;
}
@@ -331,14 +324,7 @@ class Server extends CommonDBTM
$this->initForm($ID, $options);
$this->showFormHeader($options);
$this->showFormFields($ID > 0 ? (int) $ID : null);
$this->showFormButtons($options);
return true;
}
public function showFormFields(?int $ID): void
{
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Name')) . "</td>";
echo "<td>";
@@ -388,8 +374,8 @@ class Server extends CommonDBTM
Dropdown::showFromArray(
'protocol',
[
'http' => __('HTTP', 'urbackup'),
'https' => __('HTTPS', 'urbackup'),
'http' => 'HTTP',
'https' => 'HTTPS',
],
[
'value' => $this->fields['protocol'] ?? 'http',
@@ -433,31 +419,21 @@ class Server extends CommonDBTM
echo "</td>";
echo "</tr>";
$canUpdate = Session::haveRight(self::$rightname, UPDATE);
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('API username', 'urbackup')) . "</td>";
echo "<td>";
if ($canUpdate) {
echo Html::input('api_username', [
'value' => $this->fields['api_username'] ?? '',
'size' => 40,
'autocomplete' => 'off',
]);
} else {
echo htmlspecialchars($this->fields['api_username'] ?? '');
}
echo Html::input('api_username', [
'value' => $this->fields['api_username'] ?? '',
'size' => 40,
'autocomplete' => 'off',
]);
echo "</td>";
echo "<td>" . htmlspecialchars(__('API password', 'urbackup')) . "</td>";
echo "<td>";
if ($canUpdate) {
echo "<input type='password' name='api_password' value='" .
htmlspecialchars((string) ($this->fields['api_password'] ?? '')) .
"' autocomplete='new-password'>";
} else {
echo '******';
}
echo "<input type='password' name='api_password' value='" .
htmlspecialchars((string) ($this->fields['api_password'] ?? '')) .
"' autocomplete='new-password'>";
echo "</td>";
echo "</tr>";
@@ -488,7 +464,7 @@ class Server extends CommonDBTM
echo "</td>";
echo "</tr>";
$apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
$apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('API connection status', 'urbackup')) . "<br><small class='text-muted'>" . htmlspecialchars(__('Click Save to test connection', 'urbackup')) . "</small></td>";
echo "<td colspan='3'>";
@@ -503,6 +479,10 @@ class Server extends CommonDBTM
echo "</td>";
echo "</tr>";
}
$this->showFormButtons($options);
return true;
}
/**
@@ -562,26 +542,6 @@ class Server extends CommonDBTM
public function prepareInputForAdd(mixed $input): mixed
{
if (!is_array($input)) {
return $input;
}
if (!isset($input['port']) || $input['port'] === '' || $input['port'] === null) {
$input['port'] = 55414;
}
if (!isset($input['protocol']) || $input['protocol'] === '') {
$input['protocol'] = 'http';
}
if (!isset($input['is_active']) || $input['is_active'] === '' || $input['is_active'] === null) {
$input['is_active'] = 1;
}
if (!isset($input['is_recursive']) || $input['is_recursive'] === '' || $input['is_recursive'] === null) {
$input['is_recursive'] = 0;
}
if (!isset($input['ignore_ssl']) || $input['ignore_ssl'] === '' || $input['ignore_ssl'] === null) {
$input['ignore_ssl'] = 0;
}
return $this->prepareInputForUpdate($input);
}
@@ -591,16 +551,6 @@ class Server extends CommonDBTM
return $input;
}
if (isset($input['port']) && ($input['port'] === '' || $input['port'] === null)) {
$input['port'] = 55414;
}
if (isset($input['protocol']) && $input['protocol'] === '') {
$input['protocol'] = 'http';
}
if (isset($input['ignore_ssl']) && ($input['ignore_ssl'] === '' || $input['ignore_ssl'] === null)) {
$input['ignore_ssl'] = 0;
}
if (!empty($input['id']) && (int) $input['id'] > 0) {
$server = new self();
if ($server->getFromDB((int) $input['id'])) {
@@ -830,7 +780,7 @@ class Server extends CommonDBTM
}
} catch (\Throwable $e) {
echo '<div class="alert alert-danger">';
echo htmlspecialchars(__('API Error', 'urbackup')) . ': ' . htmlspecialchars($e->getMessage());
echo 'API Error: ' . htmlspecialchars($e->getMessage());
echo '</div>';
return;
}
@@ -839,6 +789,7 @@ class Server extends CommonDBTM
echo '<thead>';
echo '<tr>';
echo '<th>' . htmlspecialchars(__('Asset', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Name', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Client name', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Version', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>';
@@ -889,14 +840,8 @@ class Server extends CommonDBTM
$itemUrl = $glpiItem ? $glpiItem->getLinkURL() : '';
echo '<tr>';
echo '<td>';
if ($itemUrl) {
echo '<a href="' . htmlspecialchars($itemUrl) . '">' . htmlspecialchars($clientName) . '</a>';
} else {
echo htmlspecialchars($clientName);
}
echo ' <small class="text-muted">(' . htmlspecialchars($itemTypeLabel) . ')</small>';
echo '</td>';
echo '<td>' . htmlspecialchars($itemTypeLabel . ' #' . $link['items_id']) . '</td>';
echo '<td>' . ($itemUrl ? '<a href="' . htmlspecialchars($itemUrl) . '">' . htmlspecialchars($clientName) . '</a>' : htmlspecialchars($clientName)) . '</td>';
echo '<td>' . htmlspecialchars($urbackupClientName) . '</td>';
echo '<td>' . htmlspecialchars($clientVersion) . '</td>';
echo '<td>' . $statusHtml . '</td>';
@@ -1004,8 +949,6 @@ class Server extends CommonDBTM
}
}
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
echo '<table class="table table-striped table-hover">';
echo '<thead>';
echo '<tr>';
@@ -1014,15 +957,13 @@ class Server extends CommonDBTM
echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Last backup', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('IP address', 'urbackup')) . '</th>';
if ($canWrite) {
echo '<th>' . htmlspecialchars(__('Actions', 'urbackup')) . '</th>';
}
echo '<th>' . htmlspecialchars(__('Actions', 'urbackup')) . '</th>';
echo '</tr>';
echo '</thead>';
echo '<tbody>';
foreach ($unlinkedClients as $uc) {
$clientName = (string) ($uc['name'] ?? __('Unknown', 'urbackup'));
$clientName = (string) ($uc['name'] ?? 'Unknown');
$clientNameLower = strtolower($clientName);
$online = $uc['online'] ?? null;
@@ -1041,367 +982,27 @@ class Server extends CommonDBTM
echo '<td>' . $statusHtml . '</td>';
echo '<td>' . htmlspecialchars($lastBackup ?: '-') . '</td>';
echo '<td>' . htmlspecialchars($clientIp ?: '-') . '</td>';
if ($canWrite) {
echo '<td>';
if (isset($linkableAssets[$clientNameLower])) {
$match = $linkableAssets[$clientNameLower];
$formAction = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php';
echo '<form method="post" action="' . htmlspecialchars($formAction) . '" class="d-inline">';
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 '<button type="submit" name="link_asset" value="1" class="btn btn-primary btn-sm">';
echo htmlspecialchars(__('Connect'));
echo '</button>';
Html::closeForm();
} else {
echo '<span class="text-muted">—</span>';
}
echo '</td>';
}
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
}
public static function showMissingClientsTab(Server $server): void
{
global $DB;
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
$apiStatus = (int) ($server->fields['last_api_status'] ?? 0);
if ($apiStatus !== 1) {
echo '<div class="alert alert-warning">';
echo htmlspecialchars(__('API connection not working. Save server to test connection.', 'urbackup'));
echo '</div>';
return;
}
$serverLocationId = (int) ($server->fields['locations_id'] ?? 0);
if ($serverLocationId <= 0) {
echo '<div class="alert alert-info">';
echo htmlspecialchars(__('No location configured for this server.', 'urbackup'));
echo '</div>';
return;
}
$rootLocationId = LocationHelper::getRootLocationId($serverLocationId);
$linkedIterator = $DB->request([
'FROM' => ServerAsset::getTable(),
]);
$linkedAssetKeys = [];
foreach ($linkedIterator as $row) {
$linkedAssetKeys[$row['itemtype'] . ':' . $row['items_id']] = true;
}
try {
$client = new UrbackupApiClient($server);
$urbackupClients = $client->getStatus();
} catch (\Throwable $e) {
echo '<div class="alert alert-danger">';
echo htmlspecialchars($e->getMessage());
echo '</div>';
return;
}
$urbackupClientNames = [];
foreach ($urbackupClients as $uc) {
$name = strtolower((string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? ''));
if ($name !== '') {
$urbackupClientNames[$name] = true;
}
}
$cacheEntity = [];
$cacheLocation = [];
$cacheState = [];
$cacheUser = [];
$cacheGroup = [];
$itemtypes = Config::getEnabledItemtypes();
$missingAssets = [];
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;
}
$iterator = $DB->request([
'FROM' => $table,
'WHERE' => [
'is_deleted' => 0,
],
]);
foreach ($iterator as $assetRow) {
$name = (string) ($assetRow['name'] ?? '');
if ($name === '') {
continue;
}
$nameLower = strtolower($name);
$assetLocationId = (int) ($assetRow['locations_id'] ?? 0);
$assetRootLocationId = LocationHelper::getRootLocationId($assetLocationId);
if ($assetRootLocationId !== $rootLocationId) {
continue;
}
$key = $itemtype . ':' . $assetRow['id'];
$assetId = (int) $assetRow['id'];
if (isset($linkedAssetKeys[$key])) {
continue;
}
if (isset($urbackupClientNames[$nameLower])) {
continue;
}
$entityName = self::getCachedName('Entity', (int) ($assetRow['entities_id'] ?? 0), $cacheEntity);
$locationName = self::getCachedLocationName($assetLocationId, $cacheLocation);
$stateName = self::getCachedName('State', (int) ($assetRow['states_id'] ?? 0), $cacheState);
$userName = self::getCachedName('User', (int) ($assetRow['users_id'] ?? 0), $cacheUser);
$groupName = self::getAssetGroupName($itemtype, $assetId, $cacheGroup);
$ip = self::getAssetIp($itemtype, $assetId);
$missingAssets[] = [
'itemtype' => $itemtype,
'items_id' => $assetId,
'name' => $name,
'entity' => $entityName,
'location' => $locationName,
'otherserial' => (string) ($assetRow['otherserial'] ?? ''),
'ip' => $ip,
'state' => $stateName,
'user' => $userName,
'group' => $groupName,
];
}
}
if (count($missingAssets) === 0) {
echo '<div class="alert alert-success">';
echo htmlspecialchars(__('All assets in this location are linked or already on the UrBackup server.', 'urbackup'));
echo '</div>';
return;
}
echo '<div class="d-flex justify-content-end mb-2">';
echo '<input type="text" id="missing-search" class="form-control form-control-sm" placeholder="' . htmlspecialchars(__('Search...', 'urbackup')) . '" style="width:250px">';
echo '</div>';
$formAction = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php';
echo '<table id="missing-table" class="table table-striped table-hover">';
echo '<thead><tr>';
echo '<th class="sortable" data-col="0">' . htmlspecialchars(__('Name')) . ' <span class="sort-arrow"></span></th>';
echo '<th class="sortable" data-col="1">' . htmlspecialchars(Entity::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
echo '<th class="sortable" data-col="2">' . htmlspecialchars(Location::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
echo '<th class="sortable" data-col="3">' . htmlspecialchars(__('Inventory number')) . ' <span class="sort-arrow"></span></th>';
echo '<th class="sortable" data-col="4">' . htmlspecialchars(__('IP address', 'urbackup')) . ' <span class="sort-arrow"></span></th>';
echo '<th class="sortable" data-col="5">' . htmlspecialchars(State::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
echo '<th class="sortable" data-col="6">' . htmlspecialchars(User::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
echo '<th class="sortable" data-col="7">' . htmlspecialchars(Group::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
if ($canWrite) {
echo '<th data-col="8">' . htmlspecialchars(__('Actions', 'urbackup')) . '</th>';
}
echo '</tr></thead>';
echo '<tbody>';
foreach ($missingAssets as $asset) {
$item = new $asset['itemtype']();
$itemLink = '';
if ($item instanceof CommonDBTM && $item->getFromDB($asset['items_id'])) {
$itemLink = $item->getLinkURL();
}
echo '<tr>';
echo '<td>';
if ($itemLink !== '') {
echo '<a href="' . htmlspecialchars($itemLink) . '">' . htmlspecialchars($asset['name']) . '</a>';
} else {
echo htmlspecialchars($asset['name']);
}
echo '</td>';
echo '<td>' . htmlspecialchars($asset['entity']) . '</td>';
echo '<td>' . htmlspecialchars($asset['location']) . '</td>';
echo '<td>' . htmlspecialchars($asset['otherserial']) . '</td>';
echo '<td>' . htmlspecialchars($asset['ip']) . '</td>';
echo '<td>' . htmlspecialchars($asset['state']) . '</td>';
echo '<td>' . htmlspecialchars($asset['user']) . '</td>';
echo '<td>' . htmlspecialchars($asset['group']) . '</td>';
if ($canWrite) {
echo '<td>';
if (isset($linkableAssets[$clientNameLower])) {
$match = $linkableAssets[$clientNameLower];
$formAction = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php';
echo '<form method="post" action="' . htmlspecialchars($formAction) . '" class="d-inline">';
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
echo Html::hidden('itemtype', ['value' => $asset['itemtype']]);
echo Html::hidden('items_id', ['value' => $asset['items_id']]);
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 '<button type="submit" name="link_asset" value="1" class="btn btn-primary btn-sm">';
echo htmlspecialchars(__('Connect'));
echo '</button>';
Html::closeForm();
echo '</td>';
} else {
echo '<span class="text-muted">—</span>';
}
echo '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo <<<'JAVASCRIPT'
<script>
$(function () {
var sortDir = {};
$('#missing-table th.sortable').on('click', function () {
var col = parseInt($(this).data('col'));
var $table = $('#missing-table');
var $tbody = $table.find('tbody');
var rows = $tbody.find('tr').get();
var dir = sortDir[col] === 'asc' ? 'desc' : 'asc';
sortDir[col] = dir;
rows.sort(function (a, b) {
var aVal = $(a).children('td').eq(col).text().trim().toLowerCase();
var bVal = $(b).children('td').eq(col).text().trim().toLowerCase();
if (aVal < bVal) return dir === 'asc' ? -1 : 1;
if (aVal > bVal) return dir === 'asc' ? 1 : -1;
return 0;
});
$.each(rows, function (i, row) { $tbody.append(row); });
$table.find('th .sort-arrow').text('');
$(this).find('.sort-arrow').text(dir === 'asc' ? '\u25B2' : '\u25BC');
});
$('#missing-search').on('keyup', function () {
var val = $(this).val().toLowerCase();
$('#missing-table tbody tr').each(function () {
var match = false;
$(this).children('td').each(function () {
if ($(this).text().toLowerCase().indexOf(val) > -1) {
match = true;
return false;
}
});
$(this).toggle(match);
});
});
});
</script>
JAVASCRIPT;
}
private static function getCachedName(string $classname, int $id, array &$cache): string
{
if ($id <= 0) {
return '';
}
if (!isset($cache[$id])) {
$fqcn = '\\' . ltrim($classname, '\\');
$obj = new $fqcn();
if ($obj->getFromDB($id)) {
$cache[$id] = (string) ($obj->fields['completename'] ?? $obj->fields['name'] ?? '');
} else {
$cache[$id] = '';
}
}
return $cache[$id];
}
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) {
return '';
}
if (!isset($cache[$id])) {
$obj = new Location();
if ($obj->getFromDB($id)) {
$cache[$id] = (string) ($obj->fields['completename'] ?? $obj->fields['name'] ?? '');
} else {
$cache[$id] = '';
}
}
return $cache[$id];
}
public static function getAssetIp(string $itemtype, int $items_id): string
{
global $DB;
$iterator = $DB->request([
'SELECT' => ['ipa.name'],
'FROM' => 'glpi_ipaddresses AS ipa',
'INNER JOIN' => [
'glpi_networknames AS nn' => [
'ON' => [
'nn' => 'items_id',
'ipa' => 'id',
['AND' => ['ipa.itemtype' => 'NetworkName']],
],
],
'glpi_networkports AS np' => [
'ON' => [
'np' => 'id',
'nn' => 'items_id',
['AND' => ['nn.itemtype' => 'NetworkPort']],
],
],
],
'WHERE' => [
'np.itemtype' => $itemtype,
'np.items_id' => $items_id,
],
'LIMIT' => 1,
]);
foreach ($iterator as $row) {
$ip = (string) ($row['name'] ?? '');
if ($ip !== '') {
return $ip;
}
}
return '';
}
}
+14 -6
View File
@@ -79,6 +79,7 @@ class ServerAsset extends CommonDBTM
}
$table = self::getTable();
$date = $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s');
$existing = self::getLinkForAsset($itemtype, $items_id, false);
@@ -87,6 +88,10 @@ class ServerAsset extends CommonDBTM
$table,
[
'plugin_urbackup_servers_id' => $server_id,
'client_name' => (string) ($item->fields['name'] ?? ''),
'client_ip' => self::extractAssetIp($item),
'is_active' => 1,
'date_mod' => $date,
],
[
'id' => (int) $existing['id'],
@@ -238,40 +243,43 @@ class ServerAsset extends CommonDBTM
echo "<div class='center'>";
echo "<table class='tab_cadre_fixehov'>";
echo "<tr><th colspan='4'>" . htmlspecialchars(__('Linked assets', 'urbackup')) . "</th></tr>";
echo "<tr><th colspan='5'>" . htmlspecialchars(__('Linked assets', 'urbackup')) . "</th></tr>";
echo "<tr>";
echo "<th>" . htmlspecialchars(__('Asset', 'urbackup')) . "</th>";
echo "<th>" . htmlspecialchars(__('Type')) . "</th>";
echo "<th>" . htmlspecialchars(__('IP address', 'urbackup')) . "</th>";
echo "<th>" . htmlspecialchars(__('Last file backup', 'urbackup')) . "</th>";
echo "<th>" . htmlspecialchars(__('Last image backup', 'urbackup')) . "</th>";
echo "</tr>";
$iterator = $DB->request([
'FROM' => self::getTable(),
'WHERE' => [
'plugin_urbackup_servers_id' => (int) $item->fields['id'],
'is_active' => 1,
],
'ORDER' => 'client_name',
]);
foreach ($iterator as $row) {
$asset_label = (string) $row['client_name'];
$itemtype = (string) $row['itemtype'];
$items_id = (int) $row['items_id'];
$asset_label = (string) $items_id;
$asset_ip = '';
if (class_exists($itemtype)) {
$asset = new $itemtype();
if ($asset instanceof CommonDBTM && $asset->getFromDB($items_id)) {
$asset_label = $asset->getLink();
$asset_ip = Server::getAssetIp($itemtype, $items_id);
}
}
echo "<tr class='tab_bg_1'>";
echo "<td>" . $asset_label . "</td>";
echo "<td>" . htmlspecialchars($itemtype) . "</td>";
echo "<td>" . htmlspecialchars($asset_ip ?: '-') . "</td>";
echo "<td>" . htmlspecialchars((string) $row['client_ip']) . "</td>";
echo "<td>" . htmlspecialchars((string) $row['last_file_backup']) . "</td>";
echo "<td>" . htmlspecialchars((string) $row['last_image_backup']) . "</td>";
echo "</tr>";
}