23 Commits

Author SHA1 Message Date
mariano f0a5be45ee traduzione e composer.json 2026-05-21 11:08:15 +02:00
mariano f0eaa76f17 change Author 2026-05-21 10:57:59 +02:00
mariano 223180d1a9 pluizia codice 2026-05-21 10:54:04 +02:00
mariano 5fac26040a change icon to ti-cloud-up 2026-05-21 10:25:21 +02:00
mariano 9ee28330ac modifica icone 2 2026-05-21 09:49:06 +02:00
mariano 8a52489b41 modifica icone 2026-05-21 09:37:58 +02:00
mariano 9979d62a2a sistemazione finale - server .php 2026-05-21 09:26:03 +02:00
mariano 01d678e855 group in list 2026-05-21 07:35:32 +02:00
mariano 0e80c1430f problema gruppo pc 2026-05-20 14:21:18 +02:00
mariano d69c465c92 modifica lista missing computer 2026-05-20 14:17:45 +02:00
mariano aba2ecf4aa modifica visualizzazione pagina server 2026-05-20 14:08:23 +02:00
mariano df28fc0018 sistemazione erore pag server.form.php 2026-05-20 13:48:33 +02:00
mariano 7a578db677 modifica pulsanti comandi 2026-05-20 13:44:44 +02:00
mariano 771409e396 tab in pagina computer.form 2026-05-20 13:39:31 +02:00
mariano 6441a8636d elimina pippo.txt 2026-05-20 13:16:07 +02:00
mariano 6a49489b73 merge all 2026-05-20 13:11:03 +02:00
mariano a5c20dc834 pippo.txt 2026-05-20 12:25:45 +02:00
mariano 6056a29865 remove debug raw data 2026-05-20 12:04:21 +02:00
mariano a20e429456 modifica redirect 2026-05-20 12:00:39 +02:00
mariano a1c3a5a07e pulsanti e create client 2026-05-20 11:46:28 +02:00
mariano f4bd50c1b1 pulsanti e sistemazione create client 2026-05-20 11:16:14 +02:00
mariano 7143d721fb fix true/false toggle switch 2026-05-20 10:26:50 +02:00
mariano fe9223a617 fix internet_enabled 2026-05-20 10:13:30 +02:00
26 changed files with 930 additions and 162 deletions
+50
View File
@@ -267,3 +267,53 @@ Il test del pulsante "Test API" continua a restituire 403 Forbidden quando acces
- `src/AssetTab.php`: Aggiunti metodi `startBackup()`, `saveInternetMode()`, `saveDefaultDirs()` - `src/AssetTab.php`: Aggiunti metodi `startBackup()`, `saveInternetMode()`, `saveDefaultDirs()`
- `src/UrbackupApiClient.php`: Aggiunti metodi `updateClientSettings()`, `saveInternetMode()` - `src/UrbackupApiClient.php`: Aggiunti metodi `updateClientSettings()`, `saveInternetMode()`
- `front/asset.form.php`: Gestione azioni per salvataggio impostazioni e backup - `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 menu `ti-hard-drives`
- `front/server.form.php`: Tab laterali, tab hash JS, breadcrumb admin, navigazione server
- `front/server.php`: Breadcrumb admin, rimosso pulsante "Add" manuale (GLPI lo genera)
- `src/Profile.php`: Icona profilo `ti-hard-drives`
- `src/AssetTab.php`: Icona tab asset `ti-hard-drives`
+23
View File
@@ -0,0 +1,23 @@
{
"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": "finstral/glpi-urbackup-plugin", "name": "glpi/urbackup-plugin",
"description": "UrBackup integration plugin for GLPI 11", "description": "UrBackup integration plugin for GLPI 11",
"type": "glpi-plugin", "type": "glpi-plugin",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
+41 -15
View File
@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
/** /**
* ------------------------------------------------------------------------- * -------------------------------------------------------------------------
* UrBackup plugin for GLPI * UrBackup plugin for GLPI
@@ -25,11 +27,11 @@ $itemtype = (string) ($_POST['itemtype'] ?? $_GET['itemtype'] ?? '');
$items_id = (int) ($_POST['items_id'] ?? $_GET['items_id'] ?? 0); $items_id = (int) ($_POST['items_id'] ?? $_GET['items_id'] ?? 0);
if ($itemtype === '' || $items_id <= 0) { if ($itemtype === '' || $items_id <= 0) {
Html::displayValidationError(__('Invalid parameters')); Html::displayValidationError(__('Invalid parameters', 'urbackup'));
} }
if (!in_array($itemtype, Config::getEnabledItemtypes(), true)) { if (!in_array($itemtype, Config::getEnabledItemtypes(), true)) {
Html::displayValidationError(__('Item type not enabled for UrBackup')); Html::displayValidationError(__('Item type not enabled for UrBackup', 'urbackup'));
} }
$item = getItemForItemtype($itemtype); $item = getItemForItemtype($itemtype);
@@ -46,13 +48,13 @@ if (isset($_POST['connect'])) {
$server_id = (int) ($_POST['plugin_urbackup_servers_id'] ?? 0); $server_id = (int) ($_POST['plugin_urbackup_servers_id'] ?? 0);
if ($server_id <= 0) { if ($server_id <= 0) {
Html::displayValidationError(__('No server selected')); Html::displayValidationError(__('No server selected', 'urbackup'));
} }
$link = ServerAsset::getLinkForAsset($itemtype, $items_id); $link = ServerAsset::getLinkForAsset($itemtype, $items_id);
if ($link !== null) { if ($link !== null) {
Html::displayValidationError(__('Asset is already linked to a server')); Html::displayValidationError(__('Asset is already linked to a server', 'urbackup'));
} }
$result = ServerAsset::createForAsset($itemtype, $items_id, $server_id); $result = ServerAsset::createForAsset($itemtype, $items_id, $server_id);
@@ -61,7 +63,7 @@ if (isset($_POST['connect'])) {
$item->getFromDB($items_id); $item->getFromDB($items_id);
Html::redirect($item->getLinkURL()); Html::redirect($item->getLinkURL());
} else { } else {
Html::displayValidationError(__('Failed to link asset to server')); Html::displayValidationError(__('Failed to link asset to server', 'urbackup'));
} }
} }
@@ -70,21 +72,13 @@ if (isset($_POST['disconnect'])) {
Html::displayRightError(); Html::displayRightError();
} }
global $DB; $result = ServerAsset::disconnectAsset($itemtype, $items_id);
$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) { if ($result) {
$item->getFromDB($items_id); $item->getFromDB($items_id);
Html::redirect($item->getLinkURL()); Html::redirect($item->getLinkURL());
} else { } else {
Html::displayValidationError(__('Failed to disconnect asset from server')); Html::displayValidationError(__('Failed to disconnect asset from server', 'urbackup'));
} }
} }
@@ -152,6 +146,38 @@ if (isset($_POST['execute'])) {
} }
} }
break; break;
case 'create_client':
if (Profile::canCurrentUser(CREATE)) {
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, false);
if ($link !== null) {
$server = new \GlpiPlugin\Urbackup\Server();
$server->getFromDB((int) $link['plugin_urbackup_servers_id']);
$client_name = ServerAsset::getAssetName($itemtype, $items_id);
try {
$api = new \GlpiPlugin\Urbackup\UrbackupApiClient($server);
$api->addClient($client_name);
} catch (\Throwable $e) {
// ignore
}
}
}
break;
case 'delete_client':
if (Profile::canCurrentUser(PURGE)) {
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, false);
if ($link !== null) {
$server = new \GlpiPlugin\Urbackup\Server();
$server->getFromDB((int) $link['plugin_urbackup_servers_id']);
$client_name = ServerAsset::getAssetName($itemtype, $items_id);
try {
$api = new \GlpiPlugin\Urbackup\UrbackupApiClient($server);
$api->removeClient($client_name);
} catch (\Throwable $e) {
// ignore
}
}
}
break;
case 'set_internet_mode': case 'set_internet_mode':
if (Profile::canCurrentUser(UPDATE)) { if (Profile::canCurrentUser(UPDATE)) {
$enabled = (int) ($_POST['internet_mode'] ?? 0) === 1; $enabled = (int) ($_POST['internet_mode'] ?? 0) === 1;
+2
View File
@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
use GlpiPlugin\Urbackup\Config; use GlpiPlugin\Urbackup\Config;
use Html; use Html;
+124 -20
View File
@@ -1,8 +1,10 @@
<?php <?php
declare(strict_types=1);
use GlpiPlugin\Urbackup\Profile; use GlpiPlugin\Urbackup\Profile;
use GlpiPlugin\Urbackup\Server; use GlpiPlugin\Urbackup\Server;
use Html; use GlpiPlugin\Urbackup\ServerAsset;
if (!defined('GLPI_ROOT')) { if (!defined('GLPI_ROOT')) {
define('GLPI_ROOT', dirname(__DIR__, 4)); define('GLPI_ROOT', dirname(__DIR__, 4));
@@ -17,6 +19,18 @@ if (!Profile::canCurrentUser(UPDATE)) {
$server = new Server(); $server = new Server();
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['link_asset'])) {
$itemtype = (string) ($_POST['itemtype'] ?? '');
$items_id = (int) ($_POST['items_id'] ?? 0);
$server_id = (int) ($_POST['id'] ?? 0);
if ($itemtype !== '' && $items_id > 0 && $server_id > 0) {
ServerAsset::connectAssetToServer($itemtype, $items_id, $server_id);
}
Html::redirect(PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php?id=' . $server_id);
}
$id = $_POST['id'] ?? 0; $id = $_POST['id'] ?? 0;
if ($id > 0) { if ($id > 0) {
@@ -35,30 +49,120 @@ $ID = $_GET['id'] ?? null;
Html::header( Html::header(
$ID ? __('Edit UrBackup server', 'urbackup') : __('Add UrBackup server', 'urbackup'), $ID ? __('Edit UrBackup server', 'urbackup') : __('Add UrBackup server', 'urbackup'),
'', '',
'Assets', 'admin',
'GlpiPlugin\Urbackup\Server' 'GlpiPlugin\Urbackup\Server'
); );
$server->showForm($ID); if ($ID > 0) {
global $DB;
if ($ID > 0 && $server->getFromDB($ID)) { $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">'; $serverIterator = $DB->request([
echo '<div class="card-header">'; 'FROM' => Server::getTable(),
echo '<h5 class="mb-0">' . htmlspecialchars(__('Unlinked clients', 'urbackup')) . '</h5>'; 'ORDER' => 'name',
echo '</div>'; ]);
echo '<div class="card-body">'; $serverIds = [];
Server::showUnlinkedClientsTab($server); foreach ($serverIterator as $row) {
echo '</div>'; $serverIds[(int) $row['id']] = (string) $row['name'];
echo '</div>'; }
$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);
} }
?>
<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();
+5 -17
View File
@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
use GlpiPlugin\Urbackup\Profile; use GlpiPlugin\Urbackup\Profile;
use GlpiPlugin\Urbackup\Server; use GlpiPlugin\Urbackup\Server;
use Html; use Html;
@@ -15,27 +17,13 @@ if (!Profile::canCurrentUser(READ)) {
Html::displayRightError(); Html::displayRightError();
} }
$can_read = Profile::canCurrentUser(READ);
$can_create = Profile::canCurrentUser(CREATE);
Html::header( Html::header(
'UrBackup Servers', __('UrBackup Servers', 'urbackup'),
'', '',
'Assets', 'admin',
'' 'GlpiPlugin\Urbackup\Server'
); );
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', [ Search::show('GlpiPlugin\Urbackup\Server', [
'is_deleted' => 0, 'is_deleted' => 0,
'massiveaction' => true, 'massiveaction' => true,
+6 -3
View File
@@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
/** /**
* AJAX endpoint for testing UrBackup API * AJAX endpoint for testing UrBackup API
*/ */
@@ -11,21 +14,21 @@ Html::header_nocache();
Session::checkLoginUser(); Session::checkLoginUser();
if (!Session::haveRight('plugin_urbackup', READ)) { if (!Session::haveRight('plugin_urbackup', READ)) {
echo json_encode(['success' => false, 'message' => 'No permission']); echo json_encode(['success' => false, 'message' => __('No permission', 'urbackup')]);
exit; exit;
} }
$server_id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0); $server_id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0);
if ($server_id <= 0) { if ($server_id <= 0) {
echo json_encode(['success' => false, 'message' => 'Invalid server ID']); echo json_encode(['success' => false, 'message' => __('Invalid server ID', 'urbackup')]);
exit; exit;
} }
$server = new GlpiPlugin\Urbackup\Server(); $server = new GlpiPlugin\Urbackup\Server();
if (!$server->getFromDB($server_id)) { if (!$server->getFromDB($server_id)) {
echo json_encode(['success' => false, 'message' => 'Server not found']); echo json_encode(['success' => false, 'message' => __('Server not found', 'urbackup')]);
exit; exit;
} }
+3 -11
View File
@@ -1,26 +1,18 @@
<?php <?php
declare(strict_types=1);
/** /**
* ------------------------------------------------------------------------- * -------------------------------------------------------------------------
* UrBackup plugin for GLPI * 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\Config;
use GlpiPlugin\Urbackup\Profile; use GlpiPlugin\Urbackup\Profile;
if (!defined('GLPI_ROOT')) { if (!defined('GLPI_ROOT')) {
die('Sorry. You cannot access this file directly.'); die(__('Sorry. You cannot access this file directly.', 'urbackup'));
} }
/** /**
+3 -4
View File
@@ -1,18 +1,17 @@
<?php <?php
declare(strict_types=1);
/** /**
* ------------------------------------------------------------------------- * -------------------------------------------------------------------------
* UrBackup plugin for GLPI * UrBackup plugin for GLPI
* ------------------------------------------------------------------------- * -------------------------------------------------------------------------
*
* Uninstall process for GLPI 11.
* -------------------------------------------------------------------------
*/ */
use GlpiPlugin\Urbackup\Profile; use GlpiPlugin\Urbackup\Profile;
if (!defined('GLPI_ROOT')) { if (!defined('GLPI_ROOT')) {
die('Sorry. You cannot access this file directly.'); die(__('Sorry. You cannot access this file directly.', 'urbackup'));
} }
/** /**
BIN
View File
Binary file not shown.
+51
View File
@@ -156,3 +156,54 @@ msgstr "Letztes Backup"
msgid "Show" msgid "Show"
msgstr "Anzeigen" 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."
BIN
View File
Binary file not shown.
+51
View File
@@ -156,3 +156,54 @@ msgstr "Last backup"
msgid "Show" msgid "Show"
msgstr "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."
BIN
View File
Binary file not shown.
+51
View File
@@ -156,3 +156,54 @@ msgstr "Ultimo backup"
msgid "Show" msgid "Show"
msgstr "Mostra" 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."
+3 -1
View File
@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
// Force OPcache to reload this file when accessed via web // Force OPcache to reload this file when accessed via web
if (PHP_SAPI !== 'cli' && function_exists('opcache_invalidate')) { if (PHP_SAPI !== 'cli' && function_exists('opcache_invalidate')) {
opcache_invalidate(__FILE__, true); opcache_invalidate(__FILE__, true);
@@ -97,7 +99,7 @@ function plugin_version_urbackup(): array
return [ return [
'name' => __('UrBackup', 'urbackup'), 'name' => __('UrBackup', 'urbackup'),
'version' => PLUGIN_URBACKUP_VERSION, 'version' => PLUGIN_URBACKUP_VERSION,
'author' => 'Finstral', 'author' => 'Mariano Benzi',
'license' => 'GPL-2.0-or-later', 'license' => 'GPL-2.0-or-later',
'homepage' => '', 'homepage' => '',
'requirements' => [ 'requirements' => [
+37 -31
View File
@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
/** /**
* ------------------------------------------------------------------------- * -------------------------------------------------------------------------
* UrBackup plugin for GLPI * UrBackup plugin for GLPI
@@ -63,7 +65,7 @@ class AssetTab extends CommonDBTM
return ''; return '';
} }
return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-upload'); return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-up');
} }
/** /**
@@ -116,8 +118,6 @@ class AssetTab extends CommonDBTM
*/ */
private static function showNoServerLinkedBlock(CommonDBTM $item): void private static function showNoServerLinkedBlock(CommonDBTM $item): void
{ {
$asset_location_id = LocationHelper::getAssetLocationId($item);
$root_location_id = LocationHelper::getRootLocationIdForAsset($item);
$is_sub_location = LocationHelper::assetIsInSubLocation($item); $is_sub_location = LocationHelper::assetIsInSubLocation($item);
$servers = LocationHelper::getAvailableServersForAsset($item); $servers = LocationHelper::getAvailableServersForAsset($item);
@@ -128,16 +128,6 @@ class AssetTab extends CommonDBTM
echo "<table class='tab_cadre_fixe'>"; echo "<table class='tab_cadre_fixe'>";
echo "<tr><th colspan='2'>" . htmlspecialchars(__('UrBackup server selection', 'urbackup')) . "</th></tr>"; echo "<tr><th colspan='2'>" . htmlspecialchars(__('UrBackup server selection', 'urbackup')) . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Asset location ID', 'urbackup')) . "</td>";
echo "<td>" . htmlspecialchars((string) $asset_location_id) . "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Root location ID', 'urbackup')) . "</td>";
echo "<td>" . htmlspecialchars((string) $root_location_id) . "</td>";
echo "</tr>";
if ($is_sub_location) { if ($is_sub_location) {
echo "<tr class='tab_bg_1'>"; echo "<tr class='tab_bg_1'>";
echo "<td colspan='2'><em>"; echo "<td colspan='2'><em>";
@@ -336,15 +326,36 @@ class AssetTab extends CommonDBTM
): void { ): void {
echo "<div class='plugin-urbackup-inner-tabs'>"; echo "<div class='plugin-urbackup-inner-tabs'>";
echo "<h3>" . htmlspecialchars(__('State', 'urbackup')) . "</h3>"; 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>';
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">';
self::showStateSection($server, $link, $api_data); self::showStateSection($server, $link, $api_data);
echo '</div>';
echo "<h3>" . htmlspecialchars(__('Actions', 'urbackup')) . "</h3>"; echo '<div class="tab-pane fade" id="actions" role="tabpanel">';
self::showActionsSection($item, $server, $link, $api_data); self::showActionsSection($item, $server, $link, $api_data);
echo '</div>';
echo "<h3>" . htmlspecialchars(__('Info / Log', 'urbackup')) . "</h3>"; echo '<div class="tab-pane fade" id="logs" role="tabpanel">';
self::showInfoLogSection($api_data); self::showInfoLogSection($api_data);
echo '</div>';
echo '</div>';
echo "</div>"; echo "</div>";
} }
@@ -418,17 +429,6 @@ class AssetTab extends CommonDBTM
echo "</table>"; echo "</table>";
echo '<details class="mt-2" style="cursor:pointer;color:#666;font-size:0.85em">';
echo '<summary>' . htmlspecialchars(__('API raw data (debug)', 'urbackup')) . '</summary>';
echo '<pre style="max-height:300px;overflow:auto;background:#f5f5f5;padding:8px;border:1px solid #ddd;font-size:0.8em">';
echo "--- client_settings ---\n\n";
echo htmlspecialchars(json_encode($settings, JSON_PRETTY_PRINT));
echo "\n\n--- client_status ---\n\n";
echo htmlspecialchars(json_encode($status, JSON_PRETTY_PRINT));
echo "\n\n--- recent_backups ---\n\n";
echo htmlspecialchars(json_encode($api_data['recent_backups'] ?? [], JSON_PRETTY_PRINT));
echo '</pre>';
echo '</details>';
} }
/** /**
@@ -488,10 +488,20 @@ class AssetTab extends CommonDBTM
echo "<tr class='tab_bg_1'>"; echo "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Backup commands', 'urbackup')) . "</td>"; echo "<td>" . htmlspecialchars(__('Backup commands', 'urbackup')) . "</td>";
echo "<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'); 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'); 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'); 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'); self::showActionButton($item, 'full_image_backup', __('Full image backup', 'urbackup'), 'btn btn-secondary');
echo "</div>";
echo "</div>";
echo "</td>"; echo "</td>";
echo "</tr>"; echo "</tr>";
@@ -707,10 +717,6 @@ class AssetTab extends CommonDBTM
echo "<textarea name='default_dirs' rows='4' cols='80'>" . htmlspecialchars($display) . "</textarea><br>"; echo "<textarea name='default_dirs' rows='4' cols='80'>" . htmlspecialchars($display) . "</textarea><br>";
echo '<div style="color:#999;font-size:0.8em">';
echo 'raw: ' . htmlspecialchars(json_encode($raw));
echo '</div>';
echo Html::submit(__('Save'), [ echo Html::submit(__('Save'), [
'name' => 'execute', 'name' => 'execute',
'class' => 'btn btn-primary', 'class' => 'btn btn-primary',
+2 -2
View File
@@ -268,7 +268,7 @@ class Config extends CommonDBTM
'checked' => $is_default, 'checked' => $is_default,
]); ]);
echo "</td>"; echo "</td>";
echo "<td><span class='badge bg-info'>Asset Definition</span></td>"; echo "<td><span class='badge bg-info'>" . htmlspecialchars(__('Asset Definition', 'urbackup')) . "</span></td>";
} else { } else {
// Legacy type // Legacy type
echo "<td>"; echo "<td>";
@@ -278,7 +278,7 @@ class Config extends CommonDBTM
]); ]);
echo "</td>"; echo "</td>";
echo "<td>" . ($is_default ? htmlspecialchars(__('Yes', 'urbackup')) : '') . "</td>"; echo "<td>" . ($is_default ? htmlspecialchars(__('Yes', 'urbackup')) : '') . "</td>";
echo "<td><span class='badge bg-secondary'>Legacy</span></td>"; echo "<td><span class='badge bg-secondary'>" . htmlspecialchars(__('Legacy', 'urbackup')) . "</span></td>";
} }
echo "</tr>"; echo "</tr>";
+2 -4
View File
@@ -88,16 +88,14 @@ class AssetController
case 'set_internet_mode': case 'set_internet_mode':
if (Profile::canCurrentUser(UPDATE)) { if (Profile::canCurrentUser(UPDATE)) {
$internet_mode = (int) $request->request->get('internet_mode', 0); $enabled = (int) $request->request->get('internet_mode', 0) === 1;
$serverAsset = new ServerAsset();
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, true); $link = ServerAsset::getLinkForAsset($itemtype, $items_id, true);
if ($link) { if ($link) {
$server = new Server(); $server = new Server();
if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) { if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) {
$api = new UrbackupApiClient($server); $api = new UrbackupApiClient($server);
$client_name = (string) ($item->fields['name'] ?? ''); $client_name = (string) ($item->fields['name'] ?? '');
$setting_key = $api->getInternetModeSettingKey(); $api->saveInternetMode($client_name, $enabled);
$api->changeClientSetting($client_name, $setting_key, $internet_mode);
} }
} }
} }
+2
View File
@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
/** /**
* ------------------------------------------------------------------------- * -------------------------------------------------------------------------
* UrBackup plugin for GLPI * UrBackup plugin for GLPI
+1 -1
View File
@@ -14,7 +14,7 @@ class Profile extends \Profile
public static function getIcon() public static function getIcon()
{ {
return 'ti ti-server'; return 'ti ti-cloud-up';
} }
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
+454 -13
View File
@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
/** /**
* ------------------------------------------------------------------------- * -------------------------------------------------------------------------
* UrBackup plugin for GLPI * UrBackup plugin for GLPI
@@ -12,9 +14,12 @@ use CommonDBTM;
use CommonGLPI; use CommonGLPI;
use Dropdown; use Dropdown;
use Entity; use Entity;
use Group;
use Html; use Html;
use Location; use Location;
use Session; use Session;
use State;
use User;
class Server extends CommonDBTM class Server extends CommonDBTM
{ {
@@ -151,7 +156,7 @@ class Server extends CommonDBTM
if (self::canView()) { if (self::canView()) {
$menu['title'] = self::getMenuName(); $menu['title'] = self::getMenuName();
$menu['page'] = self::getSearchURL(false); $menu['page'] = self::getSearchURL(false);
$menu['icon'] = 'ti ti-server'; $menu['icon'] = 'ti ti-cloud-up';
$menu['links']['search'] = self::getSearchURL(false); $menu['links']['search'] = self::getSearchURL(false);
@@ -324,7 +329,14 @@ class Server extends CommonDBTM
$this->initForm($ID, $options); $this->initForm($ID, $options);
$this->showFormHeader($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 "<tr class='tab_bg_1'>";
echo "<td>" . htmlspecialchars(__('Name')) . "</td>"; echo "<td>" . htmlspecialchars(__('Name')) . "</td>";
echo "<td>"; echo "<td>";
@@ -374,8 +386,8 @@ class Server extends CommonDBTM
Dropdown::showFromArray( Dropdown::showFromArray(
'protocol', 'protocol',
[ [
'http' => 'HTTP', 'http' => __('HTTP', 'urbackup'),
'https' => 'HTTPS', 'https' => __('HTTPS', 'urbackup'),
], ],
[ [
'value' => $this->fields['protocol'] ?? 'http', 'value' => $this->fields['protocol'] ?? 'http',
@@ -464,7 +476,7 @@ class Server extends CommonDBTM
echo "</td>"; echo "</td>";
echo "</tr>"; 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 "<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>" . htmlspecialchars(__('API connection status', 'urbackup')) . "<br><small class='text-muted'>" . htmlspecialchars(__('Click Save to test connection', 'urbackup')) . "</small></td>";
echo "<td colspan='3'>"; echo "<td colspan='3'>";
@@ -479,10 +491,6 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
echo "</td>"; echo "</td>";
echo "</tr>"; echo "</tr>";
} }
$this->showFormButtons($options);
return true;
} }
/** /**
@@ -542,6 +550,26 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
public function prepareInputForAdd(mixed $input): mixed 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); return $this->prepareInputForUpdate($input);
} }
@@ -551,6 +579,16 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
return $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['ignore_ssl']) && ($input['ignore_ssl'] === '' || $input['ignore_ssl'] === null)) {
$input['ignore_ssl'] = 0;
}
if (!empty($input['id']) && (int) $input['id'] > 0) { if (!empty($input['id']) && (int) $input['id'] > 0) {
$server = new self(); $server = new self();
if ($server->getFromDB((int) $input['id'])) { if ($server->getFromDB((int) $input['id'])) {
@@ -780,7 +818,7 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo '<div class="alert alert-danger">'; echo '<div class="alert alert-danger">';
echo 'API Error: ' . htmlspecialchars($e->getMessage()); echo htmlspecialchars(__('API Error', 'urbackup')) . ': ' . htmlspecialchars($e->getMessage());
echo '</div>'; echo '</div>';
return; return;
} }
@@ -789,7 +827,6 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
echo '<thead>'; echo '<thead>';
echo '<tr>'; echo '<tr>';
echo '<th>' . htmlspecialchars(__('Asset', 'urbackup')) . '</th>'; echo '<th>' . htmlspecialchars(__('Asset', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Name', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Client name', 'urbackup')) . '</th>'; echo '<th>' . htmlspecialchars(__('Client name', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Version', 'urbackup')) . '</th>'; echo '<th>' . htmlspecialchars(__('Version', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>'; echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>';
@@ -840,8 +877,14 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
$itemUrl = $glpiItem ? $glpiItem->getLinkURL() : ''; $itemUrl = $glpiItem ? $glpiItem->getLinkURL() : '';
echo '<tr>'; echo '<tr>';
echo '<td>' . htmlspecialchars($itemTypeLabel . ' #' . $link['items_id']) . '</td>'; echo '<td>';
echo '<td>' . ($itemUrl ? '<a href="' . htmlspecialchars($itemUrl) . '">' . htmlspecialchars($clientName) . '</a>' : htmlspecialchars($clientName)) . '</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($urbackupClientName) . '</td>'; echo '<td>' . htmlspecialchars($urbackupClientName) . '</td>';
echo '<td>' . htmlspecialchars($clientVersion) . '</td>'; echo '<td>' . htmlspecialchars($clientVersion) . '</td>';
echo '<td>' . $statusHtml . '</td>'; echo '<td>' . $statusHtml . '</td>';
@@ -904,6 +947,51 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
return; return;
} }
$serverLocationId = (int) ($server->fields['locations_id'] ?? 0);
$linkableAssets = [];
if ($serverLocationId > 0) {
$itemtypes = Config::getEnabledItemtypes();
foreach ($unlinkedClients as $uc) {
$clientName = (string) ($uc['name'] ?? '');
if ($clientName === '') {
continue;
}
$clientNameLower = strtolower($clientName);
foreach ($itemtypes as $itemtype) {
if (!class_exists($itemtype)) {
continue;
}
$assetItem = new $itemtype();
if (!$assetItem instanceof CommonDBTM) {
continue;
}
$table = $assetItem->getTable();
if (!$DB->tableExists($table)) {
continue;
}
$assetIterator = $DB->request([
'FROM' => $table,
'WHERE' => [
'name' => $clientName,
'is_deleted' => 0,
],
'LIMIT' => 1,
]);
foreach ($assetIterator as $assetRow) {
$assetLocationId = (int) ($assetRow['locations_id'] ?? 0);
$rootLocationId = LocationHelper::getRootLocationId($assetLocationId);
if ($rootLocationId > 0 && $rootLocationId === $serverLocationId) {
$linkableAssets[$clientNameLower] = [
'itemtype' => $itemtype,
'items_id' => (int) $assetRow['id'],
];
}
break;
}
}
}
}
echo '<table class="table table-striped table-hover">'; echo '<table class="table table-striped table-hover">';
echo '<thead>'; echo '<thead>';
echo '<tr>'; echo '<tr>';
@@ -912,11 +1000,15 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>'; echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Last backup', 'urbackup')) . '</th>'; echo '<th>' . htmlspecialchars(__('Last backup', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('IP address', 'urbackup')) . '</th>'; echo '<th>' . htmlspecialchars(__('IP address', 'urbackup')) . '</th>';
echo '<th>' . htmlspecialchars(__('Actions', 'urbackup')) . '</th>';
echo '</tr>'; echo '</tr>';
echo '</thead>'; echo '</thead>';
echo '<tbody>'; echo '<tbody>';
foreach ($unlinkedClients as $uc) { foreach ($unlinkedClients as $uc) {
$clientName = (string) ($uc['name'] ?? __('Unknown', 'urbackup'));
$clientNameLower = strtolower($clientName);
$online = $uc['online'] ?? null; $online = $uc['online'] ?? null;
$apiStatusString = $uc['status'] ?? ''; $apiStatusString = $uc['status'] ?? '';
$statusHtml = self::renderOnlineBadge($online, $apiStatusString); $statusHtml = self::renderOnlineBadge($online, $apiStatusString);
@@ -928,15 +1020,364 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
$clientVersion = $uc['client_version_string'] ?? $uc['client_version'] ?? '-'; $clientVersion = $uc['client_version_string'] ?? $uc['client_version'] ?? '-';
echo '<tr>'; echo '<tr>';
echo '<td>' . htmlspecialchars((string) ($uc['name'] ?? 'Unknown')) . '</td>'; echo '<td>' . htmlspecialchars($clientName) . '</td>';
echo '<td>' . htmlspecialchars($clientVersion) . '</td>'; echo '<td>' . htmlspecialchars($clientVersion) . '</td>';
echo '<td>' . $statusHtml . '</td>'; echo '<td>' . $statusHtml . '</td>';
echo '<td>' . htmlspecialchars($lastBackup ?: '-') . '</td>'; echo '<td>' . htmlspecialchars($lastBackup ?: '-') . '</td>';
echo '<td>' . htmlspecialchars($clientIp ?: '-') . '</td>'; echo '<td>' . htmlspecialchars($clientIp ?: '-') . '</td>';
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 '</tr>';
} }
echo '</tbody>'; echo '</tbody>';
echo '</table>'; echo '</table>';
} }
public static function showMissingClientsTab(Server $server): void
{
global $DB;
$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>';
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>';
echo '<td>';
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('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>';
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 '';
}
} }
+6 -14
View File
@@ -79,7 +79,6 @@ class ServerAsset extends CommonDBTM
} }
$table = self::getTable(); $table = self::getTable();
$date = $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s');
$existing = self::getLinkForAsset($itemtype, $items_id, false); $existing = self::getLinkForAsset($itemtype, $items_id, false);
@@ -88,10 +87,6 @@ class ServerAsset extends CommonDBTM
$table, $table,
[ [
'plugin_urbackup_servers_id' => $server_id, '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'], 'id' => (int) $existing['id'],
@@ -243,43 +238,40 @@ class ServerAsset extends CommonDBTM
echo "<div class='center'>"; echo "<div class='center'>";
echo "<table class='tab_cadre_fixehov'>"; echo "<table class='tab_cadre_fixehov'>";
echo "<tr><th colspan='5'>" . htmlspecialchars(__('Linked assets', 'urbackup')) . "</th></tr>"; echo "<tr><th colspan='4'>" . htmlspecialchars(__('Linked assets', 'urbackup')) . "</th></tr>";
echo "<tr>"; echo "<tr>";
echo "<th>" . htmlspecialchars(__('Asset', 'urbackup')) . "</th>"; echo "<th>" . htmlspecialchars(__('Asset', 'urbackup')) . "</th>";
echo "<th>" . htmlspecialchars(__('Type')) . "</th>"; echo "<th>" . htmlspecialchars(__('Type')) . "</th>";
echo "<th>" . htmlspecialchars(__('IP address', 'urbackup')) . "</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>"; echo "</tr>";
$iterator = $DB->request([ $iterator = $DB->request([
'FROM' => self::getTable(), 'FROM' => self::getTable(),
'WHERE' => [ 'WHERE' => [
'plugin_urbackup_servers_id' => (int) $item->fields['id'], 'plugin_urbackup_servers_id' => (int) $item->fields['id'],
'is_active' => 1,
], ],
'ORDER' => 'client_name',
]); ]);
foreach ($iterator as $row) { foreach ($iterator as $row) {
$asset_label = (string) $row['client_name'];
$itemtype = (string) $row['itemtype']; $itemtype = (string) $row['itemtype'];
$items_id = (int) $row['items_id']; $items_id = (int) $row['items_id'];
$asset_label = (string) $items_id;
$asset_ip = '';
if (class_exists($itemtype)) { if (class_exists($itemtype)) {
$asset = new $itemtype(); $asset = new $itemtype();
if ($asset instanceof CommonDBTM && $asset->getFromDB($items_id)) { if ($asset instanceof CommonDBTM && $asset->getFromDB($items_id)) {
$asset_label = $asset->getLink(); $asset_label = $asset->getLink();
$asset_ip = Server::getAssetIp($itemtype, $items_id);
} }
} }
echo "<tr class='tab_bg_1'>"; echo "<tr class='tab_bg_1'>";
echo "<td>" . $asset_label . "</td>"; echo "<td>" . $asset_label . "</td>";
echo "<td>" . htmlspecialchars($itemtype) . "</td>"; echo "<td>" . htmlspecialchars($itemtype) . "</td>";
echo "<td>" . htmlspecialchars((string) $row['client_ip']) . "</td>"; echo "<td>" . htmlspecialchars($asset_ip ?: '-') . "</td>";
echo "<td>" . htmlspecialchars((string) $row['last_file_backup']) . "</td>";
echo "<td>" . htmlspecialchars((string) $row['last_image_backup']) . "</td>";
echo "</tr>"; echo "</tr>";
} }
+6 -11
View File
@@ -37,7 +37,7 @@ class UrbackupApiClient
private string $server_version = ''; private string $server_version = '';
private bool $is_version_2_5_or_higher = false; private bool $is_version_2_4_or_higher = false;
/** /**
* Constructor. * Constructor.
@@ -52,15 +52,10 @@ class UrbackupApiClient
$this->password = (string) ($server->fields['api_password'] ?? ''); $this->password = (string) ($server->fields['api_password'] ?? '');
$this->ignore_ssl = ((int) ($server->fields['ignore_ssl'] ?? 0)) === 1; $this->ignore_ssl = ((int) ($server->fields['ignore_ssl'] ?? 0)) === 1;
$this->server_version = (string) ($server->fields['server_version'] ?? ''); $this->server_version = (string) ($server->fields['server_version'] ?? '');
$this->is_version_2_5_or_higher = $this->detectVersion2_5OrHigher(); $this->is_version_2_4_or_higher = $this->detectVersion2_4OrHigher();
} }
/** private function detectVersion2_4OrHigher(): bool
* Detect if server version is 2.5 or higher.
*
* @return bool
*/
private function detectVersion2_5OrHigher(): bool
{ {
if ($this->server_version === '') { if ($this->server_version === '') {
return false; return false;
@@ -77,7 +72,7 @@ class UrbackupApiClient
if ($major > 2) { if ($major > 2) {
return true; return true;
} }
if ($major === 2 && $minor >= 5) { if ($major === 2 && $minor >= 4) {
return true; return true;
} }
@@ -91,7 +86,7 @@ class UrbackupApiClient
*/ */
public function getInternetModeSettingKey(): string public function getInternetModeSettingKey(): string
{ {
return $this->is_version_2_5_or_higher ? 'internet_mode_enabled' : 'internet_mode'; return $this->is_version_2_4_or_higher ? 'internet_mode_enabled' : 'internet_mode';
} }
/** /**
@@ -391,7 +386,7 @@ class UrbackupApiClient
'sa' => 'clientsettings_save', 'sa' => 'clientsettings_save',
't_clientid' => $client_id, 't_clientid' => $client_id,
'overwrite' => 'true', 'overwrite' => 'true',
$key => $enabled ? '1' : '0', $key => $enabled ? 'true' : 'false',
]; ];
$data = $this->apiAction('settings', $params); $data = $this->apiAction('settings', $params);
-8
View File
@@ -7,14 +7,6 @@
<tr> <tr>
<th colspan="2">{{ __('UrBackup server selection', 'urbackup') }}</th> <th colspan="2">{{ __('UrBackup server selection', 'urbackup') }}</th>
</tr> </tr>
<tr class="tab_bg_1">
<td>{{ __('Asset location ID', 'urbackup') }}</td>
<td>{{ asset_location_id }}</td>
</tr>
<tr class="tab_bg_1">
<td>{{ __('Root location ID', 'urbackup') }}</td>
<td>{{ root_location_id }}</td>
</tr>
{% if is_sub_location %} {% if is_sub_location %}
<tr class="tab_bg_1"> <tr class="tab_bg_1">
<td colspan="2"><em> <td colspan="2"><em>