1008 lines
34 KiB
PHP
1008 lines
34 KiB
PHP
<?php
|
|
|
|
/**
|
|
* -------------------------------------------------------------------------
|
|
* UrBackup plugin for GLPI
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
namespace GlpiPlugin\Urbackup;
|
|
|
|
use CommonDBTM;
|
|
use CommonGLPI;
|
|
use Dropdown;
|
|
use Entity;
|
|
use Html;
|
|
use Location;
|
|
use Session;
|
|
|
|
class Server extends CommonDBTM
|
|
{
|
|
public static $rightname = 'plugin_urbackup';
|
|
|
|
/**
|
|
* Get table name.
|
|
*
|
|
* @param string|null $classname Class name
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function getTable($classname = null): string
|
|
{
|
|
return 'glpi_plugin_urbackup_servers';
|
|
}
|
|
|
|
/**
|
|
* Get type name.
|
|
*
|
|
* @param int $nb Number
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function getTypeName($nb = 0): string
|
|
{
|
|
return _n('UrBackup server', 'UrBackup servers', $nb, 'urbackup');
|
|
}
|
|
|
|
/**
|
|
* GLPI standard profile rights definition.
|
|
*
|
|
* This makes UrBackup appear in the standard GLPI 11 profile rights UI.
|
|
*
|
|
* @param string $interface Interface
|
|
*
|
|
* @return array<int, array<string, string>>
|
|
*/
|
|
public function getRights($interface = 'central'): array
|
|
{
|
|
return [
|
|
READ => [
|
|
'short' => __('Read'),
|
|
'long' => __('Read'),
|
|
],
|
|
UPDATE => [
|
|
'short' => __('Update'),
|
|
'long' => __('Update'),
|
|
],
|
|
CREATE => [
|
|
'short' => __('Create'),
|
|
'long' => __('Create'),
|
|
],
|
|
DELETE => [
|
|
'short' => __('Delete'),
|
|
'long' => __('Delete'),
|
|
],
|
|
PURGE => [
|
|
'short' => __('Purge'),
|
|
'long' => __('Purge'),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Check view right.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function canView(): bool
|
|
{
|
|
return Profile::canCurrentUser(READ);
|
|
}
|
|
|
|
/**
|
|
* Check create right.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function canCreate(): bool
|
|
{
|
|
return Profile::canCurrentUser(CREATE);
|
|
}
|
|
|
|
/**
|
|
* Check update right.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function canUpdate(): bool
|
|
{
|
|
return Profile::canCurrentUser(UPDATE);
|
|
}
|
|
|
|
/**
|
|
* Check delete right.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function canDelete(): bool
|
|
{
|
|
return Profile::canCurrentUser(DELETE);
|
|
}
|
|
|
|
/**
|
|
* Check purge right.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function canPurge(): bool
|
|
{
|
|
return Profile::canCurrentUser(PURGE);
|
|
}
|
|
|
|
/**
|
|
* Get menu name.
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function getMenuName(): string
|
|
{
|
|
return self::getTypeName(Session::getPluralNumber());
|
|
}
|
|
|
|
/**
|
|
* Get menu content.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function getMenuContent(): array
|
|
{
|
|
$menu = [];
|
|
|
|
if (self::canView()) {
|
|
$menu['title'] = self::getMenuName();
|
|
$menu['page'] = self::getSearchURL(false);
|
|
$menu['icon'] = 'ti ti-server';
|
|
|
|
$menu['links']['search'] = self::getSearchURL(false);
|
|
|
|
if (self::canCreate()) {
|
|
$menu['links']['add'] = self::getFormURL(false);
|
|
}
|
|
}
|
|
|
|
return $menu;
|
|
}
|
|
|
|
/**
|
|
* Define tabs.
|
|
*
|
|
* @param array<string, mixed> $options Options
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
public function defineTabs($options = []): array
|
|
{
|
|
$ong = [];
|
|
|
|
$this->addDefaultFormTab($ong);
|
|
$this->addStandardTab(ServerAsset::class, $ong, $options);
|
|
$this->addStandardTab('Log', $ong, $options);
|
|
|
|
return $ong;
|
|
}
|
|
|
|
/**
|
|
* Search options.
|
|
*
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function rawSearchOptions(): array
|
|
{
|
|
$tab = [];
|
|
|
|
$tab[] = [
|
|
'id' => 'common',
|
|
'name' => __('Characteristics'),
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 1,
|
|
'table' => self::getTable(),
|
|
'field' => 'name',
|
|
'name' => __('Name'),
|
|
'datatype' => 'itemlink',
|
|
'massiveaction' => false,
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 2,
|
|
'table' => self::getTable(),
|
|
'field' => 'ip_address',
|
|
'name' => __('IP address', 'urbackup'),
|
|
'datatype' => 'string',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 3,
|
|
'table' => self::getTable(),
|
|
'field' => 'port',
|
|
'name' => __('Network port', 'urbackup'),
|
|
'datatype' => 'integer',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 4,
|
|
'table' => self::getTable(),
|
|
'field' => 'protocol',
|
|
'name' => __('Protocol', 'urbackup'),
|
|
'datatype' => 'string',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 5,
|
|
'table' => self::getTable(),
|
|
'field' => 'server_version',
|
|
'name' => __('UrBackup server version', 'urbackup'),
|
|
'datatype' => 'string',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 6,
|
|
'table' => Entity::getTable(),
|
|
'field' => 'completename',
|
|
'name' => Entity::getTypeName(1),
|
|
'datatype' => 'dropdown',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 7,
|
|
'table' => Location::getTable(),
|
|
'field' => 'completename',
|
|
'name' => Location::getTypeName(1),
|
|
'datatype' => 'dropdown',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 8,
|
|
'table' => self::getTable(),
|
|
'field' => 'is_active',
|
|
'name' => __('Active'),
|
|
'datatype' => 'bool',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 9,
|
|
'table' => self::getTable(),
|
|
'field' => 'last_api_status',
|
|
'name' => __('Last API status', 'urbackup'),
|
|
'datatype' => 'bool',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 10,
|
|
'table' => self::getTable(),
|
|
'field' => 'last_api_check',
|
|
'name' => __('Last API check', 'urbackup'),
|
|
'datatype' => 'datetime',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 11,
|
|
'table' => self::getTable(),
|
|
'field' => 'date_creation',
|
|
'name' => __('Creation date'),
|
|
'datatype' => 'datetime',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 12,
|
|
'table' => self::getTable(),
|
|
'field' => 'date_mod',
|
|
'name' => __('Last update'),
|
|
'datatype' => 'datetime',
|
|
];
|
|
|
|
$tab[] = [
|
|
'id' => 13,
|
|
'table' => self::getTable(),
|
|
'field' => 'id',
|
|
'name' => __('View', 'urbackup'),
|
|
'massiveaction' => false,
|
|
'datatype' => 'raw',
|
|
'searchtype' => 'view',
|
|
];
|
|
|
|
return $tab;
|
|
}
|
|
|
|
/**
|
|
* Show server form.
|
|
*
|
|
* @param int $ID ID
|
|
* @param array $options Options
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function showForm($ID, array $options = []): bool
|
|
{
|
|
if ($ID > 0) {
|
|
$this->check($ID, READ);
|
|
} else {
|
|
$this->check(-1, CREATE);
|
|
$this->getEmpty();
|
|
}
|
|
|
|
$this->initForm($ID, $options);
|
|
$this->showFormHeader($options);
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(__('Name')) . "</td>";
|
|
echo "<td>";
|
|
echo Html::input('name', [
|
|
'value' => $this->fields['name'] ?? '',
|
|
'size' => 40,
|
|
]);
|
|
echo "</td>";
|
|
|
|
echo "<td>" . htmlspecialchars(__('Active')) . "</td>";
|
|
echo "<td>";
|
|
Dropdown::showYesNo('is_active', (int) ($this->fields['is_active'] ?? 1));
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(Entity::getTypeName(1)) . "</td>";
|
|
echo "<td>";
|
|
Entity::dropdown([
|
|
'name' => 'entities_id',
|
|
'value' => (int) ($this->fields['entities_id'] ?? ($_SESSION['glpiactive_entity'] ?? 0)),
|
|
]);
|
|
echo "</td>";
|
|
|
|
echo "<td>" . htmlspecialchars(__('Recursive')) . "</td>";
|
|
echo "<td>";
|
|
Dropdown::showYesNo('is_recursive', (int) ($this->fields['is_recursive'] ?? 0));
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(Location::getTypeName(1)) . "</td>";
|
|
echo "<td>";
|
|
Location::dropdown([
|
|
'name' => 'locations_id',
|
|
'value' => (int) ($this->fields['locations_id'] ?? 0),
|
|
]);
|
|
echo "<br><small>";
|
|
echo htmlspecialchars(
|
|
__('Associate the server with the main/root location. Assets in sub-locations will use this root location server.', 'urbackup')
|
|
);
|
|
echo "</small>";
|
|
echo "</td>";
|
|
|
|
echo "<td>" . htmlspecialchars(__('Protocol', 'urbackup')) . "</td>";
|
|
echo "<td>";
|
|
Dropdown::showFromArray(
|
|
'protocol',
|
|
[
|
|
'http' => 'HTTP',
|
|
'https' => 'HTTPS',
|
|
],
|
|
[
|
|
'value' => $this->fields['protocol'] ?? 'http',
|
|
]
|
|
);
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(__('IP address', 'urbackup')) . "</td>";
|
|
echo "<td>";
|
|
echo Html::input('ip_address', [
|
|
'value' => $this->fields['ip_address'] ?? '',
|
|
'size' => 40,
|
|
]);
|
|
echo "</td>";
|
|
|
|
echo "<td>" . htmlspecialchars(__('Network port', 'urbackup')) . "</td>";
|
|
echo "<td>";
|
|
echo Html::input('port', [
|
|
'value' => $this->fields['port'] ?? 55414,
|
|
'type' => 'number',
|
|
'min' => 1,
|
|
'max' => 65535,
|
|
]);
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . "</td>";
|
|
echo "<td>";
|
|
echo Html::input('server_version', [
|
|
'value' => $this->fields['server_version'] ?? '',
|
|
'size' => 30,
|
|
]);
|
|
echo "</td>";
|
|
|
|
echo "<td>" . htmlspecialchars(__('Ignore SSL verification', 'urbackup')) . "</td>";
|
|
echo "<td>";
|
|
Dropdown::showYesNo('ignore_ssl', (int) ($this->fields['ignore_ssl'] ?? 0));
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(__('API username', 'urbackup')) . "</td>";
|
|
echo "<td>";
|
|
echo Html::input('api_username', [
|
|
'value' => $this->fields['api_username'] ?? '',
|
|
'size' => 40,
|
|
'autocomplete' => 'off',
|
|
]);
|
|
echo "</td>";
|
|
|
|
echo "<td>" . htmlspecialchars(__('API password', 'urbackup')) . "</td>";
|
|
echo "<td>";
|
|
echo "<input type='password' name='api_password' value='" .
|
|
htmlspecialchars((string) ($this->fields['api_password'] ?? '')) .
|
|
"' autocomplete='new-password'>";
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(__('Comments')) . "</td>";
|
|
echo "<td colspan='3'>";
|
|
echo "<textarea name='comment' rows='5' cols='100'>" .
|
|
htmlspecialchars((string) ($this->fields['comment'] ?? '')) .
|
|
"</textarea>";
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
if ($ID > 0) {
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(__('UrBackup web interface', 'urbackup')) . "</td>";
|
|
echo "<td colspan='3'>";
|
|
|
|
$url = $this->getWebInterfaceUrl();
|
|
|
|
if ($url !== '#') {
|
|
echo "<a href='" . htmlspecialchars($url) . "' target='_blank' rel='noopener' class='btn btn-secondary'>";
|
|
echo htmlspecialchars(__('Open UrBackup interface', 'urbackup'));
|
|
echo "</a>";
|
|
} else {
|
|
echo htmlspecialchars(__('No URL available', 'urbackup'));
|
|
}
|
|
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
$apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td>" . htmlspecialchars(__('API connection status', 'urbackup')) . "<br><small class='text-muted'>" . htmlspecialchars(__('Click Save to test connection', 'urbackup')) . "</small></td>";
|
|
echo "<td colspan='3'>";
|
|
if ($apiStatus === 1) {
|
|
echo '<span class="text-success fw-bold"><i class="ti ti-check"></i> ' . htmlspecialchars(__('API connection OK', 'urbackup')) . '</span>';
|
|
} else {
|
|
echo '<span class="text-danger fw-bold"><i class="ti ti-x"></i> ' . htmlspecialchars(__('API connection failed', 'urbackup')) . '</span>';
|
|
if (!empty($this->fields['last_api_message'])) {
|
|
echo '<br><small class="text-muted">' . htmlspecialchars((string) $this->fields['last_api_message']) . '</small>';
|
|
}
|
|
}
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
}
|
|
|
|
$this->showFormButtons($options);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get web interface URL.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getWebInterfaceUrl(): string
|
|
{
|
|
$protocol = (string) ($this->fields['protocol'] ?? 'http');
|
|
$ip = (string) ($this->fields['ip_address'] ?? '');
|
|
$port = (int) ($this->fields['port'] ?? 55414);
|
|
|
|
if ($ip === '') {
|
|
return '#';
|
|
}
|
|
|
|
return sprintf('%s://%s:%d', $protocol, $ip, $port);
|
|
}
|
|
|
|
/**
|
|
* Get active servers assigned to a root location.
|
|
*
|
|
* @param int $locations_id Root location ID
|
|
*
|
|
* @return array<int, string>
|
|
*/
|
|
public static function getActiveServersForRootLocation(int $locations_id): array
|
|
{
|
|
global $DB;
|
|
|
|
$servers = [];
|
|
|
|
if ($locations_id <= 0) {
|
|
return $servers;
|
|
}
|
|
|
|
if (!$DB->tableExists(self::getTable())) {
|
|
return $servers;
|
|
}
|
|
|
|
$iterator = $DB->request([
|
|
'FROM' => self::getTable(),
|
|
'WHERE' => [
|
|
'locations_id' => $locations_id,
|
|
'is_active' => 1,
|
|
],
|
|
'ORDER' => 'name',
|
|
]);
|
|
|
|
foreach ($iterator as $row) {
|
|
$servers[(int) $row['id']] = (string) $row['name'];
|
|
}
|
|
|
|
return $servers;
|
|
}
|
|
|
|
public function prepareInputForAdd(mixed $input): mixed
|
|
{
|
|
return $this->prepareInputForUpdate($input);
|
|
}
|
|
|
|
public function prepareInputForUpdate(mixed $input): mixed
|
|
{
|
|
if (!is_array($input)) {
|
|
return $input;
|
|
}
|
|
|
|
if (!empty($input['id']) && (int) $input['id'] > 0) {
|
|
$server = new self();
|
|
if ($server->getFromDB((int) $input['id'])) {
|
|
$serverFields = $server->fields;
|
|
$ip = $input['ip_address'] ?? $serverFields['ip_address'] ?? '';
|
|
$port = $input['port'] ?? $serverFields['port'] ?? 55414;
|
|
$protocol = $input['protocol'] ?? $serverFields['protocol'] ?? 'http';
|
|
$apiUsername = $input['api_username'] ?? $serverFields['api_username'] ?? '';
|
|
$apiPassword = $input['api_password'] ?? $serverFields['api_password'] ?? '';
|
|
$ignoreSsl = $input['ignore_ssl'] ?? $serverFields['ignore_ssl'] ?? 0;
|
|
|
|
if ($ip !== '') {
|
|
$tmpServer = new self();
|
|
$tmpServer->fields = [
|
|
'id' => (int) $input['id'],
|
|
'ip_address' => $ip,
|
|
'port' => $port,
|
|
'protocol' => $protocol,
|
|
'api_username' => $apiUsername,
|
|
'api_password' => $apiPassword,
|
|
'ignore_ssl' => $ignoreSsl,
|
|
];
|
|
|
|
try {
|
|
$client = new UrbackupApiClient($tmpServer);
|
|
$result = $client->testConnection();
|
|
$input['last_api_status'] = $result['success'] ? 1 : 0;
|
|
$input['last_api_message'] = $result['message'] ?? '';
|
|
$input['last_api_check'] = date('Y-m-d H:i:s');
|
|
} catch (\Throwable $e) {
|
|
$input['last_api_status'] = 0;
|
|
$input['last_api_message'] = $e->getMessage();
|
|
$input['last_api_check'] = date('Y-m-d H:i:s');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $input;
|
|
}
|
|
|
|
public function testApiConnection(): array
|
|
{
|
|
$ip = (string) ($this->fields['ip_address'] ?? '');
|
|
|
|
if ($ip === '') {
|
|
return [
|
|
'status' => 'no_ip',
|
|
'html' => '<span class="text-muted">' . htmlspecialchars(__('No IP address configured', 'urbackup')) . '</span>',
|
|
];
|
|
}
|
|
|
|
try {
|
|
$client = new UrbackupApiClient($this);
|
|
$result = $client->testConnection();
|
|
|
|
$this->update([
|
|
'id' => (int) $this->fields['id'],
|
|
'last_api_status' => $result['success'] ? 1 : 0,
|
|
'last_api_message' => $result['message'] ?? '',
|
|
'last_api_check' => date('Y-m-d H:i:s'),
|
|
]);
|
|
|
|
if ($result['success']) {
|
|
return [
|
|
'status' => 'ok',
|
|
'html' => '<span class="text-success fw-bold"><i class="ti ti-check"></i> ' .
|
|
htmlspecialchars(__('API connection OK', 'urbackup')) . '</span>',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'status' => 'failed',
|
|
'html' => '<span class="text-danger fw-bold"><i class="ti ti-x"></i> ' .
|
|
htmlspecialchars(__('API connection failed', 'urbackup')) . '</span><br>' .
|
|
'<small class="text-muted">' . htmlspecialchars($result['message'] ?? '') . '</small>',
|
|
];
|
|
} catch (\Throwable $e) {
|
|
$message = $e->getMessage();
|
|
$isUnreachable = $this->isNetworkError($message);
|
|
|
|
return [
|
|
'status' => $isUnreachable ? 'unreachable' : 'failed',
|
|
'html' => '<span class="' . ($isUnreachable ? 'text-warning' : 'text-danger') . ' fw-bold">' .
|
|
'<i class="ti ' . ($isUnreachable ? 'ti-wifi-off' : 'ti-x') . '"></i> ' .
|
|
htmlspecialchars($isUnreachable ? __('Server unreachable', 'urbackup') : __('API connection failed', 'urbackup')) .
|
|
'</span><br>' .
|
|
'<small class="text-muted">' . htmlspecialchars($message) . '</small>',
|
|
];
|
|
}
|
|
}
|
|
|
|
private function isNetworkError(string $message): bool
|
|
{
|
|
$networkKeywords = [
|
|
'timeout',
|
|
'could not resolve host',
|
|
'couldn\'t connect to host',
|
|
'connection refused',
|
|
'connection timed out',
|
|
'network is unreachable',
|
|
'no route to host',
|
|
'ssl',
|
|
'certificate',
|
|
'curl error',
|
|
'request failed',
|
|
'returned HTTP status',
|
|
'returned non-JSON response',
|
|
'problem with the ssl certificate',
|
|
'ssl certificate problem',
|
|
'ssl connect error',
|
|
'ssl wrong version',
|
|
];
|
|
|
|
$lowerMessage = strtolower($message);
|
|
|
|
foreach ($networkKeywords as $keyword) {
|
|
if (str_contains($lowerMessage, strtolower($keyword))) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (preg_match('/http status [45]\d{2}/', $lowerMessage)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static function renderOnlineBadge(mixed $online, mixed $statusString): string
|
|
{
|
|
$online = match (true) {
|
|
$online === true || $online === 1 || $online === '1' || $online === 'true' => 1,
|
|
$online === false || $online === 0 || $online === '0' || $online === 'false' => 0,
|
|
default => null,
|
|
};
|
|
|
|
$parts = [];
|
|
|
|
if ($online === 1) {
|
|
$parts[] = '<span class="text-success fw-bold"><i class="ti ti-player-play"></i> '
|
|
. htmlspecialchars(__('Online', 'urbackup')) . '</span>';
|
|
} elseif ($online === 0) {
|
|
$parts[] = '<span class="text-muted"><i class="ti ti-player-pause"></i> '
|
|
. htmlspecialchars(__('Offline', 'urbackup')) . '</span>';
|
|
} else {
|
|
$parts[] = '<span class="text-muted">-</span>';
|
|
}
|
|
|
|
$statusString = is_string($statusString) ? trim((string) $statusString) : '';
|
|
if ($statusString !== '') {
|
|
$badgeClass = 'secondary';
|
|
if (strtolower($statusString) === 'ok') {
|
|
$badgeClass = 'success';
|
|
} elseif (in_array(strtolower($statusString), ['minor_problems', 'minor problems'], true)) {
|
|
$badgeClass = 'warning';
|
|
} elseif (in_array(strtolower($statusString), ['major_problems', 'major problems', 'error'], true)) {
|
|
$badgeClass = 'danger';
|
|
} elseif (strtolower($statusString) === 'paused') {
|
|
$badgeClass = 'secondary';
|
|
}
|
|
$parts[] = '<span class="badge bg-' . $badgeClass . ' ms-1">'
|
|
. htmlspecialchars($statusString) . '</span>';
|
|
}
|
|
|
|
return implode(' ', $parts);
|
|
}
|
|
|
|
private static function formatLastBackup(mixed $value): string
|
|
{
|
|
if ($value === null || $value === '' || $value === 0 || $value === '0') {
|
|
return '';
|
|
}
|
|
|
|
if (is_numeric($value)) {
|
|
$timestamp = (int) $value;
|
|
if ($timestamp > 0 && $timestamp < 2000000000) {
|
|
return date('Y-m-d H:i:s', $timestamp);
|
|
}
|
|
return (string) $value;
|
|
}
|
|
|
|
return (string) $value;
|
|
}
|
|
|
|
public static function showLinkedClientsTab(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;
|
|
}
|
|
|
|
$iterator = $DB->request([
|
|
'FROM' => 'glpi_plugin_urbackup_serverassets',
|
|
'WHERE' => [
|
|
'plugin_urbackup_servers_id' => (int) $server->fields['id'],
|
|
],
|
|
]);
|
|
|
|
$linkedAssets = [];
|
|
foreach ($iterator as $row) {
|
|
$linkedAssets[] = $row;
|
|
}
|
|
|
|
if (count($linkedAssets) === 0) {
|
|
echo '<div class="alert alert-info">';
|
|
echo htmlspecialchars(__('No linked assets', 'urbackup'));
|
|
echo '</div>';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$client = new UrbackupApiClient($server);
|
|
$urbackupClients = $client->getStatus();
|
|
|
|
if (empty($urbackupClients)) {
|
|
echo '<div class="alert alert-warning">';
|
|
echo htmlspecialchars(__('No clients found on UrBackup server', 'urbackup'));
|
|
echo '</div>';
|
|
return;
|
|
}
|
|
} catch (\Throwable $e) {
|
|
echo '<div class="alert alert-danger">';
|
|
echo 'API Error: ' . htmlspecialchars($e->getMessage());
|
|
echo '</div>';
|
|
return;
|
|
}
|
|
|
|
echo '<table class="table table-striped table-hover">';
|
|
echo '<thead>';
|
|
echo '<tr>';
|
|
echo '<th>' . htmlspecialchars(__('Asset', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Name', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Client name', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Version', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Last backup', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('IP address', 'urbackup')) . '</th>';
|
|
echo '</tr>';
|
|
echo '</thead>';
|
|
echo '<tbody>';
|
|
|
|
foreach ($linkedAssets as $link) {
|
|
$glpiItem = null;
|
|
if (!empty($link['itemtype']) && !empty($link['items_id'])) {
|
|
$itemClass = $link['itemtype'];
|
|
if (class_exists($itemClass)) {
|
|
$glpiItem = new $itemClass();
|
|
$glpiItem->getFromDB((int) $link['items_id']);
|
|
}
|
|
}
|
|
|
|
$clientName = $glpiItem ? ($glpiItem->fields['name'] ?? '') : '';
|
|
$urbackupClient = null;
|
|
|
|
foreach ($urbackupClients as $uc) {
|
|
$ucName = (string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? '');
|
|
if (strcasecmp($ucName, $clientName) === 0) {
|
|
$urbackupClient = $uc;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$online = $urbackupClient ? ($urbackupClient['online'] ?? null) : null;
|
|
$apiStatusString = $urbackupClient ? ($urbackupClient['status'] ?? '') : '';
|
|
$statusHtml = self::renderOnlineBadge($online, $apiStatusString);
|
|
|
|
$lastBackupRaw = $urbackupClient
|
|
? ($urbackupClient['lastbackup'] ?? $urbackupClient['lastbackup_time'] ?? $urbackupClient['last_backup'] ?? $urbackupClient['last_backup_time'] ?? $urbackupClient['file_lastbackup'] ?? $urbackupClient['image_lastbackup'] ?? '')
|
|
: '';
|
|
$lastBackup = self::formatLastBackup($lastBackupRaw);
|
|
|
|
$clientIp = $urbackupClient
|
|
? ($urbackupClient['client_ip'] ?? $urbackupClient['ip'] ?? $urbackupClient['ip_address'] ?? $urbackupClient['clientaddress'] ?? '')
|
|
: '';
|
|
|
|
$clientVersion = $urbackupClient ? ($urbackupClient['client_version_string'] ?? $urbackupClient['client_version'] ?? '-') : '-';
|
|
$urbackupClientName = $urbackupClient ? ($urbackupClient['name'] ?? '-') : '-';
|
|
|
|
$itemTypeLabel = !empty($link['itemtype']) ? (class_exists($link['itemtype']) ? $link['itemtype']::getTypeName(1) : $link['itemtype']) : '';
|
|
$itemUrl = $glpiItem ? $glpiItem->getLinkURL() : '';
|
|
|
|
echo '<tr>';
|
|
echo '<td>' . htmlspecialchars($itemTypeLabel . ' #' . $link['items_id']) . '</td>';
|
|
echo '<td>' . ($itemUrl ? '<a href="' . htmlspecialchars($itemUrl) . '">' . htmlspecialchars($clientName) . '</a>' : htmlspecialchars($clientName)) . '</td>';
|
|
echo '<td>' . htmlspecialchars($urbackupClientName) . '</td>';
|
|
echo '<td>' . htmlspecialchars($clientVersion) . '</td>';
|
|
echo '<td>' . $statusHtml . '</td>';
|
|
echo '<td>' . htmlspecialchars($lastBackup ?: '-') . '</td>';
|
|
echo '<td>' . htmlspecialchars($clientIp ?: '-') . '</td>';
|
|
echo '</tr>';
|
|
}
|
|
|
|
echo '</tbody>';
|
|
echo '</table>';
|
|
}
|
|
|
|
public static function showUnlinkedClientsTab(Server $server): void
|
|
{
|
|
$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;
|
|
}
|
|
|
|
global $DB;
|
|
|
|
$iterator = $DB->request([
|
|
'FROM' => 'glpi_plugin_urbackup_serverassets',
|
|
]);
|
|
|
|
$linkedNames = [];
|
|
foreach ($iterator as $row) {
|
|
$assetName = ServerAsset::getAssetName($row['itemtype'], (int) $row['items_id']);
|
|
if ($assetName !== '') {
|
|
$linkedNames[] = strtolower($assetName);
|
|
}
|
|
}
|
|
|
|
try {
|
|
$client = new UrbackupApiClient($server);
|
|
$urbackupClients = $client->getStatus();
|
|
} catch (\Throwable $e) {
|
|
echo '<div class="alert alert-danger">';
|
|
echo htmlspecialchars($e->getMessage());
|
|
echo '</div>';
|
|
return;
|
|
}
|
|
|
|
$unlinkedClients = [];
|
|
foreach ($urbackupClients as $uc) {
|
|
$name = strtolower((string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? ''));
|
|
if (!in_array($name, $linkedNames, true)) {
|
|
$unlinkedClients[] = $uc;
|
|
}
|
|
}
|
|
|
|
if (count($unlinkedClients) === 0) {
|
|
echo '<div class="alert alert-info">';
|
|
echo htmlspecialchars(__('No unlinked clients found on UrBackup server', 'urbackup'));
|
|
echo '</div>';
|
|
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 '<thead>';
|
|
echo '<tr>';
|
|
echo '<th>' . htmlspecialchars(__('Name', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Version', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Last backup', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('IP address', 'urbackup')) . '</th>';
|
|
echo '<th>' . htmlspecialchars(__('Actions', 'urbackup')) . '</th>';
|
|
echo '</tr>';
|
|
echo '</thead>';
|
|
echo '<tbody>';
|
|
|
|
foreach ($unlinkedClients as $uc) {
|
|
$clientName = (string) ($uc['name'] ?? 'Unknown');
|
|
$clientNameLower = strtolower($clientName);
|
|
|
|
$online = $uc['online'] ?? null;
|
|
$apiStatusString = $uc['status'] ?? '';
|
|
$statusHtml = self::renderOnlineBadge($online, $apiStatusString);
|
|
|
|
$lastBackupRaw = $uc['lastbackup'] ?? $uc['lastbackup_time'] ?? $uc['last_backup'] ?? $uc['last_backup_time'] ?? $uc['file_lastbackup'] ?? $uc['image_lastbackup'] ?? '';
|
|
$lastBackup = self::formatLastBackup($lastBackupRaw);
|
|
|
|
$clientIp = $uc['client_ip'] ?? $uc['ip'] ?? $uc['ip_address'] ?? $uc['clientaddress'] ?? '';
|
|
$clientVersion = $uc['client_version_string'] ?? $uc['client_version'] ?? '-';
|
|
|
|
echo '<tr>';
|
|
echo '<td>' . htmlspecialchars($clientName) . '</td>';
|
|
echo '<td>' . htmlspecialchars($clientVersion) . '</td>';
|
|
echo '<td>' . $statusHtml . '</td>';
|
|
echo '<td>' . htmlspecialchars($lastBackup ?: '-') . '</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 '</tbody>';
|
|
echo '</table>';
|
|
}
|
|
} |