913 lines
33 KiB
PHP
913 lines
33 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
/**
|
||
|
|
* -------------------------------------------------------------------------
|
||
|
|
* UrBackup plugin for GLPI
|
||
|
|
* -------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
|
||
|
|
namespace GlpiPlugin\Urbackup;
|
||
|
|
|
||
|
|
use CommonDBTM;
|
||
|
|
use CommonGLPI;
|
||
|
|
use Dropdown;
|
||
|
|
use Html;
|
||
|
|
use Session;
|
||
|
|
use Throwable;
|
||
|
|
|
||
|
|
class AssetTab extends CommonDBTM
|
||
|
|
{
|
||
|
|
public static $rightname = 'plugin_urbackup';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Extract actual value from a UrBackup setting struct.
|
||
|
|
*
|
||
|
|
* API returns settings as: {"use":N, "value":..., "value_client":..., "value_group":...}
|
||
|
|
*/
|
||
|
|
private static function extractSettingValue(mixed $setting, mixed $default = null): mixed
|
||
|
|
{
|
||
|
|
if (is_array($setting) && array_key_exists('value', $setting)) {
|
||
|
|
return $setting['value'];
|
||
|
|
}
|
||
|
|
|
||
|
|
return $setting ?? $default;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get type name.
|
||
|
|
*
|
||
|
|
* @param int $nb Number
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function getTypeName($nb = 0): string
|
||
|
|
{
|
||
|
|
return __('UrBackup', 'urbackup');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get tab name for item.
|
||
|
|
*
|
||
|
|
* @param CommonGLPI $item Item
|
||
|
|
* @param int $withtemplate With template
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0): string
|
||
|
|
{
|
||
|
|
if (!Config::isItemtypeEnabled($item::class)) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!Profile::canCurrentUser(READ)) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-upload');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Display tab content for item.
|
||
|
|
*
|
||
|
|
* @param CommonGLPI $item Item
|
||
|
|
* @param int $tabnum Tab number
|
||
|
|
* @param int $withtemplate With template
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0): bool
|
||
|
|
{
|
||
|
|
if (!Config::isItemtypeEnabled($item::class)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!Profile::canCurrentUser(READ)) {
|
||
|
|
echo "<div class='alert alert-warning'>";
|
||
|
|
echo htmlspecialchars(__('You do not have permission to view UrBackup information.', 'urbackup'));
|
||
|
|
echo "</div>";
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
$itemtype = $item::class;
|
||
|
|
$items_id = (int) ($item->fields['id'] ?? 0);
|
||
|
|
|
||
|
|
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, true);
|
||
|
|
|
||
|
|
echo "<div class='plugin-urbackup-asset-tab'>";
|
||
|
|
|
||
|
|
if ($link === null) {
|
||
|
|
self::showNoServerLinkedBlock($item);
|
||
|
|
} else {
|
||
|
|
self::showServerLinkedBlock($item, $link);
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "</div>";
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show block when no server is linked.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset item
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showNoServerLinkedBlock(CommonDBTM $item): void
|
||
|
|
{
|
||
|
|
$asset_location_id = LocationHelper::getAssetLocationId($item);
|
||
|
|
$root_location_id = LocationHelper::getRootLocationIdForAsset($item);
|
||
|
|
$is_sub_location = LocationHelper::assetIsInSubLocation($item);
|
||
|
|
$servers = LocationHelper::getAvailableServersForAsset($item);
|
||
|
|
|
||
|
|
echo "<div class='alert alert-info'>";
|
||
|
|
echo htmlspecialchars(__('No UrBackup server linked.', 'urbackup'));
|
||
|
|
echo "</div>";
|
||
|
|
|
||
|
|
echo "<table class='tab_cadre_fixe'>";
|
||
|
|
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) {
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td colspan='2'><em>";
|
||
|
|
echo htmlspecialchars(
|
||
|
|
__('The asset is in a sub-location. The plugin will use the server assigned to the root location.', 'urbackup')
|
||
|
|
);
|
||
|
|
echo "</em></td>";
|
||
|
|
echo "</tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (count($servers) === 0) {
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td colspan='2'>";
|
||
|
|
echo "<div class='alert alert-warning'>";
|
||
|
|
echo htmlspecialchars(__('No UrBackup server available for the root location of this asset.', 'urbackup'));
|
||
|
|
echo "</div>";
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
echo "</table>";
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) {
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td colspan='2'>";
|
||
|
|
echo htmlspecialchars(__('A server is available, but you do not have permission to link this asset.', 'urbackup'));
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
echo "</table>";
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Available servers for root location', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>";
|
||
|
|
|
||
|
|
echo "<form method='post' action='" . htmlspecialchars(PLUGIN_URBACKUP_WEB_DIR . "/front/asset.form.php") . "'>";
|
||
|
|
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
|
||
|
|
echo Html::hidden('itemtype', ['value' => $item::class]);
|
||
|
|
echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]);
|
||
|
|
|
||
|
|
Dropdown::showFromArray(
|
||
|
|
'plugin_urbackup_servers_id',
|
||
|
|
$servers,
|
||
|
|
[
|
||
|
|
'value' => 0,
|
||
|
|
]
|
||
|
|
);
|
||
|
|
|
||
|
|
echo Html::submit(__('Connect', 'urbackup'), [
|
||
|
|
'name' => 'connect',
|
||
|
|
'class' => 'btn btn-primary',
|
||
|
|
]);
|
||
|
|
|
||
|
|
Html::closeForm();
|
||
|
|
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
echo "</table>";
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show block when server is linked.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset item
|
||
|
|
* @param array<string, mixed> $link Link row
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showServerLinkedBlock(CommonDBTM $item, array $link): void
|
||
|
|
{
|
||
|
|
$server = new Server();
|
||
|
|
$server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0);
|
||
|
|
|
||
|
|
if ($server_id <= 0 || !$server->getFromDB($server_id)) {
|
||
|
|
echo "<div class='alert alert-danger'>";
|
||
|
|
echo htmlspecialchars(__('The linked UrBackup server no longer exists.', 'urbackup'));
|
||
|
|
echo "</div>";
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$api_data = self::loadApiData($item, $server, $link);
|
||
|
|
|
||
|
|
echo "<table class='tab_cadre_fixe'>";
|
||
|
|
echo "<tr><th colspan='4'>" . htmlspecialchars(__('UrBackup status', 'urbackup')) . "</th></tr>";
|
||
|
|
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Linked server', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>" . $server->getLink() . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('IP address', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars((string) $server->fields['ip_address']) . "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars((string) ($server->fields['server_version'] ?? '')) . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Client name', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars(ServerAsset::getAssetName($item::class, (int) $item->fields['id'])) . "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
|
||
|
|
echo "</table>";
|
||
|
|
|
||
|
|
if ($api_data['error'] !== '') {
|
||
|
|
echo "<div class='alert alert-warning'>";
|
||
|
|
echo htmlspecialchars($api_data['error']);
|
||
|
|
echo "</div>";
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($api_data['ip_warning'] !== '') {
|
||
|
|
echo "<div class='alert alert-warning'>";
|
||
|
|
echo htmlspecialchars($api_data['ip_warning']);
|
||
|
|
echo "</div>";
|
||
|
|
}
|
||
|
|
|
||
|
|
self::showInternalTabs($item, $server, $link, $api_data);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Load API data for asset tab.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset
|
||
|
|
* @param Server $server Server
|
||
|
|
* @param array<string, mixed> $link Link
|
||
|
|
*
|
||
|
|
* @return array<string, mixed>
|
||
|
|
*/
|
||
|
|
private static function loadApiData(CommonDBTM $item, Server $server, array $link): array
|
||
|
|
{
|
||
|
|
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||
|
|
$asset_ip = ServerAsset::extractAssetIp($item);
|
||
|
|
|
||
|
|
$data = [
|
||
|
|
'client_found' => false,
|
||
|
|
'client_status' => [],
|
||
|
|
'client_settings' => [],
|
||
|
|
'authkey' => '',
|
||
|
|
'recent_backups' => [],
|
||
|
|
'logs' => [],
|
||
|
|
'error' => '',
|
||
|
|
'ip_warning' => '',
|
||
|
|
];
|
||
|
|
|
||
|
|
try {
|
||
|
|
$api = new UrbackupApiClient($server);
|
||
|
|
$client_status = $api->getClientStatusByName($client_name);
|
||
|
|
|
||
|
|
if ($client_status !== null) {
|
||
|
|
$data['client_found'] = true;
|
||
|
|
$data['client_status'] = $client_status;
|
||
|
|
|
||
|
|
$urbackup_ip = (string) (
|
||
|
|
$client_status['ip'] ??
|
||
|
|
$client_status['ip_address'] ??
|
||
|
|
$client_status['addr'] ??
|
||
|
|
''
|
||
|
|
);
|
||
|
|
|
||
|
|
if ($asset_ip !== '' && $urbackup_ip !== '' && $asset_ip !== $urbackup_ip) {
|
||
|
|
$data['ip_warning'] = sprintf(
|
||
|
|
__('Client name matches, but GLPI IP "%s" differs from UrBackup IP "%s".', 'urbackup'),
|
||
|
|
$asset_ip,
|
||
|
|
$urbackup_ip
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
$data['client_settings'] = $api->getClientSettings($client_name);
|
||
|
|
$data['authkey'] = $api->getClientAuthKey($client_name);
|
||
|
|
$data['recent_backups'] = $api->getRecentBackups($client_name, 10);
|
||
|
|
$data['logs'] = $api->getClientLogs($client_name, 50);
|
||
|
|
}
|
||
|
|
} catch (Throwable $e) {
|
||
|
|
$data['error'] = $e->getMessage();
|
||
|
|
}
|
||
|
|
|
||
|
|
return $data;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show internal sections.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset
|
||
|
|
* @param Server $server Server
|
||
|
|
* @param array<string, mixed> $link Link
|
||
|
|
* @param array<string, mixed> $api_data API data
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showInternalTabs(
|
||
|
|
CommonDBTM $item,
|
||
|
|
Server $server,
|
||
|
|
array $link,
|
||
|
|
array $api_data
|
||
|
|
): void {
|
||
|
|
echo "<div class='plugin-urbackup-inner-tabs'>";
|
||
|
|
|
||
|
|
echo "<h3>" . htmlspecialchars(__('State', 'urbackup')) . "</h3>";
|
||
|
|
self::showStateSection($server, $link, $api_data);
|
||
|
|
|
||
|
|
echo "<h3>" . htmlspecialchars(__('Actions', 'urbackup')) . "</h3>";
|
||
|
|
self::showActionsSection($item, $server, $link, $api_data);
|
||
|
|
|
||
|
|
echo "<h3>" . htmlspecialchars(__('Info / Log', 'urbackup')) . "</h3>";
|
||
|
|
self::showInfoLogSection($api_data);
|
||
|
|
|
||
|
|
echo "</div>";
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show state section.
|
||
|
|
*
|
||
|
|
* @param Server $server Server
|
||
|
|
* @param array<string, mixed> $link Link
|
||
|
|
* @param array<string, mixed> $api_data API data
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showStateSection(Server $server, array $link, array $api_data): void
|
||
|
|
{
|
||
|
|
$status = $api_data['client_status'];
|
||
|
|
$settings = $api_data['client_settings'];
|
||
|
|
|
||
|
|
echo "<table class='tab_cadre_fixe'>";
|
||
|
|
echo "<tr><th colspan='2'>" . htmlspecialchars(__('Client state', 'urbackup')) . "</th></tr>";
|
||
|
|
|
||
|
|
if (!$api_data['client_found']) {
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td colspan='2'>";
|
||
|
|
echo htmlspecialchars(__('Client not found on UrBackup server.', 'urbackup'));
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
echo "</table>";
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$internetMode = self::extractSettingValue($settings['internet_mode_enabled'] ?? $settings['internet_mode'] ?? null, 0);
|
||
|
|
$internetModeDisplay = ((int) $internetMode === 1)
|
||
|
|
? '<span class="badge bg-success">' . __('Yes', 'urbackup') . '</span>'
|
||
|
|
: '<span class="badge bg-secondary">' . __('No', 'urbackup') . '</span>';
|
||
|
|
|
||
|
|
$rows = [
|
||
|
|
__('Client version', 'urbackup') => $status['client_version_string'] ?? $status['client_version'] ?? $status['version'] ?? '-',
|
||
|
|
__('Online / Offline', 'urbackup') => self::formatOnlineStatus($status),
|
||
|
|
__('Last file backup', 'urbackup') => self::formatTimestamp($status['file_lastbackup'] ?? $status['lastbackup'] ?? $status['last_file_backup'] ?? ''),
|
||
|
|
__('Last image backup', 'urbackup') => self::formatTimestamp($status['image_lastbackup'] ?? $status['lastbackup_image'] ?? $status['last_image_backup'] ?? ''),
|
||
|
|
__('Last file backup result', 'urbackup') => self::formatBoolStatus($status['file_ok'] ?? null),
|
||
|
|
__('Last image backup result', 'urbackup') => self::formatBoolStatus($status['image_ok'] ?? null),
|
||
|
|
__('Current activities', 'urbackup') => $status['status'] ?? $status['activity'] ?? '-',
|
||
|
|
__('Internet mode', 'urbackup') => $internetModeDisplay,
|
||
|
|
];
|
||
|
|
|
||
|
|
foreach ($rows as $label => $value) {
|
||
|
|
$displayValue = is_array($value) ? json_encode($value) : (string) $value;
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars((string) $label) . "</td>";
|
||
|
|
echo "<td>" . $displayValue . "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Internet authentication key', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>";
|
||
|
|
$authKey = (string) $api_data['authkey'];
|
||
|
|
if ($authKey === '') {
|
||
|
|
echo '<span class="text-muted">-</span>';
|
||
|
|
} else {
|
||
|
|
echo '<input type="password" class="form-control" value="' . htmlspecialchars($authKey) . '" readonly id="urbackup-authkey">';
|
||
|
|
echo '<div class="form-check mt-2">';
|
||
|
|
echo '<input type="checkbox" class="form-check-input" id="urbackup-show-key" onchange="document.getElementById(\'urbackup-authkey\').type = this.checked ? \'text\' : \'password\'">';
|
||
|
|
echo '<label class="form-check-label" for="urbackup-show-key">' . __('Show', 'urbackup') . '</label>';
|
||
|
|
echo '</div>';
|
||
|
|
}
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
|
||
|
|
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>';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show actions section.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset
|
||
|
|
* @param Server $server Server
|
||
|
|
* @param array<string, mixed> $link Link
|
||
|
|
* @param array<string, mixed> $api_data API data
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showActionsSection(
|
||
|
|
CommonDBTM $item,
|
||
|
|
Server $server,
|
||
|
|
array $link,
|
||
|
|
array $api_data
|
||
|
|
): void {
|
||
|
|
echo "<table class='tab_cadre_fixe'>";
|
||
|
|
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>";
|
||
|
|
echo "<td>";
|
||
|
|
self::showActionButton($item, 'create_client', __('Create client in UrBackup', 'urbackup'), 'btn btn-primary');
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Profile::canCurrentUser(UPDATE)) {
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Internet mode', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>";
|
||
|
|
self::showInternetModeForm($item, $api_data);
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Default directories', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>";
|
||
|
|
self::showDefaultDirsForm($item, $api_data);
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Backup commands', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>";
|
||
|
|
self::showActionButton($item, 'incremental_file_backup', __('Incremental file backup', 'urbackup'), 'btn btn-secondary');
|
||
|
|
self::showActionButton($item, 'full_file_backup', __('Full file backup', 'urbackup'), 'btn btn-secondary');
|
||
|
|
self::showActionButton($item, 'incremental_image_backup', __('Incremental image backup', 'urbackup'), 'btn btn-secondary');
|
||
|
|
self::showActionButton($item, 'full_image_backup', __('Full image backup', 'urbackup'), 'btn btn-secondary');
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Disconnect client', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>";
|
||
|
|
self::showDisconnectButton($item);
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Profile::canCurrentUser(PURGE)) {
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(__('Delete client from UrBackup server', 'urbackup')) . "</td>";
|
||
|
|
echo "<td>";
|
||
|
|
echo "<div class='alert alert-warning'>";
|
||
|
|
echo htmlspecialchars(
|
||
|
|
__('The client deletion will be queued on the UrBackup server and may require up to 24 hours.', 'urbackup')
|
||
|
|
);
|
||
|
|
echo "</div>";
|
||
|
|
self::showActionButton($item, 'delete_client', __('Delete client from UrBackup server', 'urbackup'), 'btn btn-danger');
|
||
|
|
echo "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "</table>";
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show info/log section.
|
||
|
|
*
|
||
|
|
* @param array<string, mixed> $api_data API data
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function formatBytes(mixed $bytes): string
|
||
|
|
{
|
||
|
|
$bytes = (float) ($bytes ?? 0);
|
||
|
|
if ($bytes <= 0) {
|
||
|
|
return '-';
|
||
|
|
}
|
||
|
|
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||
|
|
$i = floor(log($bytes, 1024));
|
||
|
|
$i = min((int) $i, count($units) - 1);
|
||
|
|
return sprintf('%.1f %s', $bytes / pow(1024, $i), $units[$i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show info/log section.
|
||
|
|
*
|
||
|
|
* @param array<string, mixed> $api_data API data
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showInfoLogSection(array $api_data): void
|
||
|
|
{
|
||
|
|
$recent_backups = $api_data['recent_backups'] ?? [];
|
||
|
|
usort($recent_backups, function ($a, $b) {
|
||
|
|
$tsA = (int) ($a['time'] ?? $a['backuptime'] ?? $a['backup_time'] ?? $a['created'] ?? $a['start_time'] ?? 0);
|
||
|
|
$tsB = (int) ($b['time'] ?? $b['backuptime'] ?? $b['backup_time'] ?? $b['created'] ?? $b['start_time'] ?? 0);
|
||
|
|
return $tsB - $tsA;
|
||
|
|
});
|
||
|
|
|
||
|
|
echo "<table class='tab_cadre_fixe'>";
|
||
|
|
echo "<tr><th colspan='6'>" . htmlspecialchars(__('Recent backups', 'urbackup')) . "</th></tr>";
|
||
|
|
echo "<tr>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Type')) . "</th>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Date')) . "</th>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Result')) . "</th>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Size')) . "</th>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Incremental', 'urbackup')) . "</th>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Backup ID', 'urbackup')) . "</th>";
|
||
|
|
echo "</tr>";
|
||
|
|
|
||
|
|
foreach ($recent_backups as $backup) {
|
||
|
|
$incremental = (int) ($backup['incremental'] ?? 0);
|
||
|
|
$timestamp = $backup['time'] ?? $backup['backuptime'] ?? $backup['backup_time'] ?? $backup['created'] ?? $backup['start_time'] ?? 0;
|
||
|
|
$dateFormatted = self::formatTimestamp($timestamp);
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars((string) ($backup['backup_type'] ?? '')) . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars($dateFormatted) . "</td>";
|
||
|
|
echo "<td><span class='text-success'><i class='ti ti-circle-check'></i> " . htmlspecialchars(__('Success', 'urbackup')) . "</span></td>";
|
||
|
|
echo "<td>" . htmlspecialchars(self::formatBytes($backup['size'] ?? $backup['size_bytes'] ?? 0)) . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars($incremental === 1 ? __('Yes', 'urbackup') : __('No', 'urbackup')) . "</td>";
|
||
|
|
echo "<td><code>" . htmlspecialchars((string) ($backup['backupid'] ?? $backup['id'] ?? '-')) . "</code></td>";
|
||
|
|
echo "</tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (count($api_data['recent_backups']) === 0) {
|
||
|
|
echo "<tr class='tab_bg_1'><td colspan='6'>";
|
||
|
|
echo htmlspecialchars(__('No recent backup information available.', 'urbackup'));
|
||
|
|
echo "</td></tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "</table>";
|
||
|
|
|
||
|
|
echo "<br>";
|
||
|
|
|
||
|
|
$logs = array_reverse($api_data['logs'] ?? []);
|
||
|
|
|
||
|
|
echo "<table class='tab_cadre_fixe'>";
|
||
|
|
echo "<tr><th colspan='3'>" . htmlspecialchars(__('Client logs', 'urbackup')) . "</th></tr>";
|
||
|
|
echo "<tr>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Date')) . "</th>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Level')) . "</th>";
|
||
|
|
echo "<th>" . htmlspecialchars(__('Message')) . "</th>";
|
||
|
|
echo "</tr>";
|
||
|
|
|
||
|
|
foreach ($logs as $log) {
|
||
|
|
echo "<tr class='tab_bg_1'>";
|
||
|
|
echo "<td>" . htmlspecialchars(self::formatTimestamp($log['time'] ?? $log['created'] ?? '')) . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars((string) ($log['level'] ?? $log['severity'] ?? '')) . "</td>";
|
||
|
|
echo "<td>" . htmlspecialchars((string) ($log['message'] ?? $log['msg'] ?? $log['text'] ?? '')) . "</td>";
|
||
|
|
echo "</tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (count($api_data['logs']) === 0) {
|
||
|
|
echo "<tr class='tab_bg_1'><td colspan='3'>";
|
||
|
|
echo htmlspecialchars(__('No client logs available.', 'urbackup'));
|
||
|
|
echo "</td></tr>";
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "</table>";
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show generic UrBackup action button.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset
|
||
|
|
* @param string $action Action
|
||
|
|
* @param string $label Button label
|
||
|
|
* @param string $class CSS class
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showActionButton(CommonDBTM $item, string $action, string $label, string $class): void
|
||
|
|
{
|
||
|
|
echo "<form method='post' action='" . htmlspecialchars(PLUGIN_URBACKUP_WEB_DIR . "/front/asset.form.php") . "' class='plugin-urbackup-inline-form'>";
|
||
|
|
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
|
||
|
|
echo Html::hidden('itemtype', ['value' => $item::class]);
|
||
|
|
echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]);
|
||
|
|
echo Html::hidden('urbackup_action', ['value' => $action]);
|
||
|
|
echo Html::submit($label, [
|
||
|
|
'name' => 'execute',
|
||
|
|
'class' => $class,
|
||
|
|
]);
|
||
|
|
Html::closeForm();
|
||
|
|
echo " ";
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show internet mode form.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset
|
||
|
|
* @param array<string, mixed> $api_data API data
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showInternetModeForm(CommonDBTM $item, array $api_data): void
|
||
|
|
{
|
||
|
|
$settings = $api_data['client_settings'];
|
||
|
|
$current = (int) self::extractSettingValue(
|
||
|
|
$settings['internet_mode_enabled'] ?? $settings['internet_mode'] ?? null,
|
||
|
|
0
|
||
|
|
);
|
||
|
|
|
||
|
|
echo "<form method='post' action='" . htmlspecialchars(PLUGIN_URBACKUP_WEB_DIR . "/front/asset.form.php") . "' class='plugin-urbackup-inline-form'>";
|
||
|
|
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
|
||
|
|
echo Html::hidden('itemtype', ['value' => $item::class]);
|
||
|
|
echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]);
|
||
|
|
echo Html::hidden('urbackup_action', ['value' => 'set_internet_mode']);
|
||
|
|
echo Html::hidden('execute', ['value' => '1']);
|
||
|
|
echo Html::hidden('internet_mode', ['value' => '0']);
|
||
|
|
|
||
|
|
echo '<div class="form-check form-switch">';
|
||
|
|
echo '<input class="form-check-input" type="checkbox" name="internet_mode" value="1" id="internet-mode-toggle" role="switch"'
|
||
|
|
. ($current ? ' checked' : '')
|
||
|
|
. ' onchange="this.form.submit()">';
|
||
|
|
echo '<label class="form-check-label" for="internet-mode-toggle">'
|
||
|
|
. htmlspecialchars(__('Enable Internet mode', 'urbackup'))
|
||
|
|
. '</label>';
|
||
|
|
echo '</div>';
|
||
|
|
|
||
|
|
Html::closeForm();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show default directories form.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset
|
||
|
|
* @param array<string, mixed> $api_data API data
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showDefaultDirsForm(CommonDBTM $item, array $api_data): void
|
||
|
|
{
|
||
|
|
$settings = $api_data['client_settings'];
|
||
|
|
$raw = $settings['default_dirs'] ?? '';
|
||
|
|
|
||
|
|
$display = '';
|
||
|
|
if (is_array($raw)) {
|
||
|
|
// API returns struct: {"use":N,"value":"paths","value_client":"","value_group":""}
|
||
|
|
$display = (string) ($raw['value'] ?? '');
|
||
|
|
} else {
|
||
|
|
$display = (string) $raw;
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "<form method='post' action='" . htmlspecialchars(PLUGIN_URBACKUP_WEB_DIR . "/front/asset.form.php") . "'>";
|
||
|
|
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
|
||
|
|
echo Html::hidden('itemtype', ['value' => $item::class]);
|
||
|
|
echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]);
|
||
|
|
echo Html::hidden('urbackup_action', ['value' => 'set_default_dirs']);
|
||
|
|
|
||
|
|
echo "<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'), [
|
||
|
|
'name' => 'execute',
|
||
|
|
'class' => 'btn btn-primary',
|
||
|
|
]);
|
||
|
|
|
||
|
|
Html::closeForm();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show disconnect button.
|
||
|
|
*
|
||
|
|
* @param CommonDBTM $item Asset
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function showDisconnectButton(CommonDBTM $item): void
|
||
|
|
{
|
||
|
|
echo "<form method='post' action='" . htmlspecialchars(PLUGIN_URBACKUP_WEB_DIR . "/front/asset.form.php") . "'>";
|
||
|
|
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
|
||
|
|
echo Html::hidden('itemtype', ['value' => $item::class]);
|
||
|
|
echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]);
|
||
|
|
|
||
|
|
echo Html::submit(__('Disconnect', 'urbackup'), [
|
||
|
|
'name' => 'disconnect',
|
||
|
|
'class' => 'btn btn-warning',
|
||
|
|
]);
|
||
|
|
|
||
|
|
Html::closeForm();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format online status.
|
||
|
|
*
|
||
|
|
* @param array<string, mixed> $status Status
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
private static function formatOnlineStatus(array $status): string
|
||
|
|
{
|
||
|
|
$online = $status['online'] ?? $status['is_online'] ?? null;
|
||
|
|
|
||
|
|
return match (true) {
|
||
|
|
$online === true || $online === 1 || $online === '1' || $online === 'true' => __('Online', 'urbackup'),
|
||
|
|
$online === false || $online === 0 || $online === '0' || $online === 'false' => __('Offline', 'urbackup'),
|
||
|
|
default => '',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format bool status.
|
||
|
|
*
|
||
|
|
* @param mixed $value Value
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
private static function formatBoolStatus(mixed $value): string
|
||
|
|
{
|
||
|
|
return match (true) {
|
||
|
|
$value === true || $value === 1 || $value === '1' || $value === 'true' => __('OK', 'urbackup'),
|
||
|
|
$value === false || $value === 0 || $value === '0' || $value === 'false' => __('Failed', 'urbackup'),
|
||
|
|
default => '',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format timestamp-like value.
|
||
|
|
*
|
||
|
|
* @param mixed $value Timestamp
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
private static function formatTimestamp(mixed $value): string
|
||
|
|
{
|
||
|
|
if ($value === null || $value === '' || $value === 0 || $value === '0') {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$ts = (int) $value;
|
||
|
|
if ($ts > 0 && $ts < 2000000000) {
|
||
|
|
// Unix timestamp
|
||
|
|
return Html::convDateTime(date('Y-m-d H:i:s', $ts));
|
||
|
|
}
|
||
|
|
|
||
|
|
$str = trim((string) $value);
|
||
|
|
if ($str === '') {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
// ISO date string like "2024-01-15T10:30:00" or "2024-01-15 10:30:00"
|
||
|
|
$normalized = str_replace('T', ' ', $str);
|
||
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/', $normalized)) {
|
||
|
|
return Html::convDateTime($normalized);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Plain date like "2024-01-15"
|
||
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $str)) {
|
||
|
|
return Html::convDate($str);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function startBackup(CommonDBTM $item, string $type): bool
|
||
|
|
{
|
||
|
|
$link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false);
|
||
|
|
if ($link === null) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0);
|
||
|
|
if ($server_id <= 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$server = new Server();
|
||
|
|
if (!$server->getFromDB($server_id)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||
|
|
if ($client_name === '') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
$api = new UrbackupApiClient($server);
|
||
|
|
|
||
|
|
switch ($type) {
|
||
|
|
case 'file':
|
||
|
|
return $api->startIncrementalFileBackup($client_name);
|
||
|
|
case 'image':
|
||
|
|
return $api->startIncrementalImageBackup($client_name);
|
||
|
|
}
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function saveInternetMode(CommonDBTM $item, bool $enabled): bool
|
||
|
|
{
|
||
|
|
$link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false);
|
||
|
|
if ($link === null) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0);
|
||
|
|
if ($server_id <= 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$server = new Server();
|
||
|
|
if (!$server->getFromDB($server_id)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||
|
|
if ($client_name === '') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
$api = new UrbackupApiClient($server);
|
||
|
|
return $api->saveInternetMode($client_name, $enabled);
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function saveDefaultDirs(CommonDBTM $item, string $dirs): bool
|
||
|
|
{
|
||
|
|
$link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false);
|
||
|
|
if ($link === null) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0);
|
||
|
|
if ($server_id <= 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$server = new Server();
|
||
|
|
if (!$server->getFromDB($server_id)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||
|
|
if ($client_name === '') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
$api = new UrbackupApiClient($server);
|
||
|
|
return $api->updateClientSettings($client_name, 'default_dirs', trim($dirs));
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|