Compare commits
1 Commits
9582554dfe
..
pippo
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c1676dd47 |
@@ -0,0 +1,66 @@
|
|||||||
|
# Istruzioni per lo Sviluppo PHP 8.3 & Symfony
|
||||||
|
|
||||||
|
Sei un esperto Senior Developer specializzato in Symfony (6.4+) e PHP 8.3.
|
||||||
|
|
||||||
|
## Regole del Codice (PHP 8.3)
|
||||||
|
|
||||||
|
- Usa sempre la **Constructor Promotion** per la Dependency Injection.
|
||||||
|
|
||||||
|
- Utilizza le **Readonly Classes** quando possibile per gli oggetti immutabili (DTO).
|
||||||
|
|
||||||
|
- Applica **Typed Class Constants** (novità PHP 8.3).
|
||||||
|
|
||||||
|
- Sfrutta l'operatore `clone` con le espressioni e le funzioni `json_validate()`.
|
||||||
|
|
||||||
|
- Rigorosa tipizzazione: usa `declare(strict_types=1);` in ogni nuovo file.
|
||||||
|
|
||||||
|
## Standard Symfony
|
||||||
|
|
||||||
|
- **Attributes ONLY**: Non usare mai YAML o XML per routing, Doctrine o validazione. Usa solo PHP Attributes.
|
||||||
|
|
||||||
|
- **Service Container**: Prediligi l'autowiring.
|
||||||
|
|
||||||
|
- **Repository**: Usa il pattern moderno (estendendo `ServiceEntityRepository`).
|
||||||
|
|
||||||
|
- **Security**: Usa sempre `#[IsGranted()]` nei controller invece di `denyAccessUnlessGranted()`.
|
||||||
|
|
||||||
|
## Workflow di Risoluzione Errori
|
||||||
|
|
||||||
|
1. Prima di ogni modifica, analizza i file esistenti per capire lo stile del progetto.
|
||||||
|
|
||||||
|
2. Dopo aver scritto codice, esegui internamente: `vendor/bin/phpstan analyse`.
|
||||||
|
|
||||||
|
3. Se ci sono errori di stile, correggi con: `vendor/bin/ecs check --fix`.
|
||||||
|
|
||||||
|
4. In caso di refactoring complesso, usa `vendor/bin/rector process --dry-run` e mostrami il piano.
|
||||||
|
|
||||||
|
## Memoria degli Errori
|
||||||
|
|
||||||
|
- Leggi sempre il file `FIX_HISTORY.md` per non ripetere bug già risolti in passato.
|
||||||
|
|
||||||
|
- Ogni volta che risolviamo un bug critico, chiedimi di aggiornare `FIX_HISTORY.md`.
|
||||||
|
|
||||||
|
|
||||||
|
## Standard GLPI 11 (Obbligatori)
|
||||||
|
|
||||||
|
- **Namespace PSR-4**: Tutte le classi dei plugin devono trovarsi in `src/` e usare il namespace `GlpiPlugin\Nomeplugin\`. La cartella `inc/` è deprecata.
|
||||||
|
|
||||||
|
- **Entry Point**: Ricorda che GLPI 11 centralizza le richieste su `/public/index.php`. Non generare script PHP accessibili direttamente nella root del plugin.
|
||||||
|
|
||||||
|
- **Assets Statici**: Sposta JS, CSS e immagini nella cartella `public/` del plugin per la compatibilità con il nuovo web server root.
|
||||||
|
|
||||||
|
- **Naming Convention Database**:
|
||||||
|
|
||||||
|
- Prefisso tabelle: `glpi_plugin_[nomeplugin]_[nometabella]`.
|
||||||
|
|
||||||
|
- Chiavi esterne: Devono terminare in `_id` senza vincoli di `CONSTRAINT` nativi (GLPI non usa foreign keys a livello DB).
|
||||||
|
|
||||||
|
- **Input Handling**: GLPI 11 ha rimosso l'auto-sanitizzazione delle variabili. Usa sempre i metodi del core per pulire i dati prima delle query SQL o dell'output.
|
||||||
|
|
||||||
|
## Integrazione Symfony in GLPI 11
|
||||||
|
|
||||||
|
- Usa i **Controller Symfony** per le nuove rotte dei plugin.
|
||||||
|
|
||||||
|
- Sfrutta il **Twig Template Engine** situato in `templates/` per la UI.
|
||||||
|
|
||||||
|
- Definisci i comandi CLI tramite il componente Console di Symfony.
|
||||||
Executable
+27
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Fix file permissions for GLPI plugin
|
||||||
|
echo "Fixing file permissions..."
|
||||||
|
|
||||||
|
# Fix ownership
|
||||||
|
sudo chown www-data:www-data /var/www/glpi/plugins/urbackup/src/Server.php
|
||||||
|
sudo chown www-data:www-data /var/www/glpi/plugins/urbackup/src/Profile.php
|
||||||
|
sudo chown www-data:www-data /var/www/glpi/plugins/urbackup/src/MassiveAction.php
|
||||||
|
sudo chown www-data:www-data /var/www/glpi/plugins/urbackup/src/AssetTab.php
|
||||||
|
sudo chown www-data:www-data /var/www/glpi/plugins/urbackup/hook.php
|
||||||
|
sudo chown www-data:www-data /var/www/glpi/plugins/urbackup/setup.php
|
||||||
|
|
||||||
|
# Replace hook.php with corrected version
|
||||||
|
if [ -f /var/www/glpi/plugins/urbackup/hook_corrected.php ]; then
|
||||||
|
sudo cp /var/www/glpi/plugins/urbackup/hook_corrected.php /var/www/glpi/plugins/urbackup/hook.php
|
||||||
|
sudo chown www-data:www-data /var/www/glpi/plugins/urbackup/hook.php
|
||||||
|
rm /var/www/glpi/plugins/urbackup/hook_corrected.php
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fix all src/ files
|
||||||
|
sudo chown www-data:www-data /var/www/glpi/plugins/urbackup/src/*.php
|
||||||
|
|
||||||
|
echo "Permissions fixed. Now add declare(strict_types=1) to:"
|
||||||
|
echo " - src/Server.php"
|
||||||
|
echo " - src/Profile.php"
|
||||||
|
echo " - src/MassiveAction.php"
|
||||||
|
echo " - src/AssetTab.php"
|
||||||
@@ -1,94 +1,269 @@
|
|||||||
# MEMORY.md - Stato del Plugin UrBackup
|
# UrBackup Plugin - Memoria di Sviluppo
|
||||||
|
|
||||||
## Ultima modifica: 28/05/2026
|
## 📋 Informazioni Plugin
|
||||||
|
|
||||||
## GLPI 11 — Session::checkCSRF() breaking change
|
- **Nome**: UrBackup
|
||||||
In GLPI 11, `Session::checkCSRF()` richiede il primo argomento `$data` (i dati POST da validare). In GLPI ≤10 accettava zero argomenti.
|
- **Versione**: 0.4.2
|
||||||
Il listener globale `CheckCsrfListener` gestisce già il CSRF per tutte le richieste POST, ma il plugin chiamava `Session::checkCSRF()` senza argomenti causando errore fatale.
|
- **Compatibilità**: GLPI 11.0.6+, PHP 8.3+
|
||||||
|
- **Namespace**: `GlpiPlugin\Urbackup`
|
||||||
|
|
||||||
### Fix applicati (tutti i file)
|
## ✅ Funzionalità Implementate
|
||||||
- RIMOSSE tutte le chiamate esplicite `Session::checkCSRF()` dai 4 file front — GLPI 11 le gestisce già globalmente via `CheckCsrfListener`
|
|
||||||
- Il listener globale consuma il token CSRF, quindi una seconda chiamata dal plugin fallisce perché il token non è più valido
|
|
||||||
- `public/js/urbackup.js` — aggiunto header `X-Glpi-Csrf-Token` con `getAjaxCsrfToken()` per le richieste AJAX
|
|
||||||
|
|
||||||
## Performance - Asset Definition vs Computer
|
### Installazione/Disinstallazione
|
||||||
|
- ✅ Installazione plugin tramite CLI: `php bin/console glpi:plugin:install urbackup`
|
||||||
|
- ✅ Attivazione: `php bin/console glpi:plugin:activate urbackup`
|
||||||
|
- ✅ Disinstallazione: `php bin/console glpi:plugin:uninstall urbackup`
|
||||||
|
|
||||||
### Problema
|
### Struttura File
|
||||||
Gli Asset Definition custom (GLPI 11) sono 2-5x più lenti dei Computer nativi nel caricamento del tab UrBackup.
|
- `setup.php`: Hook di inizializzazione, menu, tab, versione, prerequisiti
|
||||||
|
- `hook.php`: Funzioni di installazione (spostate in setup.php per OPcache)
|
||||||
|
- `src/`: Classi namespaced con namespace `GlpiPlugin\Urbackup`
|
||||||
|
- `install/`: Script di installazione e disinstallazione database
|
||||||
|
- `front/`: Pagine PHP per lista server e form
|
||||||
|
- `ajax/`: Endpoint AJAX per test API (`server_test.php`)
|
||||||
|
|
||||||
### Causa
|
### Classi Principali
|
||||||
L'overhead è in GLPI 11 core, non nel plugin:
|
| Classe | Descrizione |
|
||||||
1. **`Asset::__construct()`** — itera 30+ Capacity classi per ogni istanza
|
|--------|-------------|
|
||||||
2. **`Asset::post_getFromDB()`** — decodifica JSON custom fields e li processa
|
| `Config` | Configurazione plugin, tipi asset abilitati |
|
||||||
3. **`eval()` autoloading** — le classi concrete sono definite via `eval()` a runtime
|
| `Profile` | Diritti utente, permessi |
|
||||||
|
| `Server` | Server UrBackup (CRUD) |
|
||||||
|
| `ServerAsset` | Linking asset-server |
|
||||||
|
| `AssetTab` | Tab visualizzato sugli asset |
|
||||||
|
| `MassiveAction` | Azioni massive |
|
||||||
|
| `UrbackupApiClient` | Client API per comunicazione con UrBackup server |
|
||||||
|
| `LocationHelper` | Helper per assegnazione server basata su location |
|
||||||
|
| `Controller/ServerController` | Symfony controller per routes server |
|
||||||
|
| `Controller/AssetController` | Symfony controller per azioni asset |
|
||||||
|
| `Command/TestApiCommand` | CLI command per test API |
|
||||||
|
|
||||||
### Ottimizzazioni applicate
|
### Pagine Frontend
|
||||||
1. **`AssetTab.php::loadApiData()`** — letto `$item->fields['name']` direttamente invece di chiamare `ServerAsset::getAssetName()` che faceva una seconda `getFromDB()` ridondante
|
- `front/config.form.php`: Pagina configurazione plugin
|
||||||
2. **`AssetTab.php::startBackup()`, `saveInternetMode()`, `saveDefaultDirs()`, `showServerLinkedBlock()`** — stesso pattern, `$item->fields['name']` al posto di `getAssetName()`
|
- `front/server.php`: Lista server UrBackup
|
||||||
3. **`Server.php::showMissingClientsTab()`** — batch loading IP e gruppi: `batchLoadIps()` (1 query per itemtype vs 1 per riga) + `batchLoadGroups()` (1 query per itemtype vs 1 per riga)
|
- `front/server.form.php`: Form aggiunta/modifica server
|
||||||
|
- `front/asset.form.php`: Form azioni su asset (connect/disconnect/start backup)
|
||||||
|
- `ajax/server_test.php`: Endpoint AJAX per test connessione API
|
||||||
|
|
||||||
## Architettura
|
### Interfacce GLPI
|
||||||
- `src/Capacity/UrBackupCapacity.php` — registra `AssetTab` via `CommonGLPI::registerStandardTab()` in `onClassBootstrap()`
|
- ✅ Menu: Amministrazione → Server UrBackup
|
||||||
- `setup.php` — registra capacità, CSS, JS, hook
|
- ✅ Tab: UrBackup su Computer e asset abilitati
|
||||||
- `src/AssetTab.php` — display tab content + tab interni (Stato/Azioni/Info-Log)
|
- ✅ Configurazione: Abilitazione tipi asset (legacy + Asset Definition GLPI 11)
|
||||||
- `src/ServerAsset.php` — gestione collegamenti asset-server
|
|
||||||
- `src/Config.php` — itemtype enabled check
|
|
||||||
- `src/UrbackupApiClient.php` — client API con caching in-memory (per istanza) e sessione
|
|
||||||
- `src/LocationHelper.php` — risoluzione location radice
|
|
||||||
- `src/Server.php` — CRUD server, tab missing clients, form
|
|
||||||
|
|
||||||
## Asset Tab Interni
|
## 🔧 Fix e Correzioni Applicate
|
||||||
- 3 sub-tab: Stato, Azioni (solo UPDATE/CREATE), Info/Log
|
|
||||||
- CSS: tab con tonalità di grigio differenti (scuro/medio/chiaro)
|
|
||||||
- Caricamento dati API con caching sessione 30s
|
|
||||||
- Dati caricati: status, settings, authkey, backup recenti (10), log (50)
|
|
||||||
|
|
||||||
## Cache
|
1. **OPcache**: Funzioni install in setup.php (non in hook.php) per problema OPcache
|
||||||
- `UrbackupApiClient`: cache in-memory per `getStatus()` e `getClientSettings()`
|
2. **Duplicate profilerights**: Aggiunto check in `Profile::registerRights()` per evitare duplicati
|
||||||
- `AssetTab::loadApiData()`: cache sessione 30s (chiave: server_id + client_name)
|
3. **CSRF**: Include `inc/includes.php` nei file front per gestione CSRF
|
||||||
- API timeout: 30s, connect timeout: 5s
|
4. **Firme metodi**: Corretto `getTabNameForItem(CommonGLPI, int)` e `displayTabContentForItem(CommonGLPI, int, int)`
|
||||||
|
5. **AssetTab incompleto**: Completato metodo `formatTimestamp()` mancante
|
||||||
|
6. **RIGHT_NAME costante inesistente**: Sostituito `Profile::RIGHT_NAME` con stringa `'plugin_urbackup'` in AssetTab, ServerAsset e MassiveAction. La costante non esiste nel plugin, era un errore di copia-incolla.
|
||||||
|
7. **Profile self-reference**: Corretto `displayTabContentForItem()` per usare `\Profile` (core GLPI) invece di `Profile` (plugin)
|
||||||
|
8. **UrbackupApiClient riscritto completamente**: Basato sul Python wrapper, con le seguenti correzioni chiave:
|
||||||
|
- Session passata sia nell'URL che nel body POST
|
||||||
|
- Content-Type: `application/x-www-form-urlencoded; charset=UTF-8`
|
||||||
|
- Autenticazione PBKDF2 compatibile con server UrBackup
|
||||||
|
- Login flow: anonymous → salt → login con hash
|
||||||
|
- Gestione errore `{"error": 1}` per username inesistente
|
||||||
|
9. **`htmlescape()` non esistente**: Sostituito con `htmlspecialchars()` in tutti i file (AssetTab, Server, ServerAsset, MassiveAction, Config). La funzione `htmlescape()` non è definita in GLPI né in PHP.
|
||||||
|
10. **File `front/asset.form.php` mancante**: Creato per gestire connect/disconnect/start_backup dal tab asset.
|
||||||
|
11. **File debug/junk rimossi**: Eliminati debug_session.php, hook_corrected.php, rector.php, KILL_VED.php, front/debug.php.
|
||||||
|
12. **`initProfile()` obbligatorio**: Reso `$profile` nullable (`$profile = null`) per compatibilità con GLPI che chiama `initProfile()` senza argomenti.
|
||||||
|
|
||||||
## Bugfix: Campi API username/password non visibili in nuova installazione
|
### Modifica Test API Connection (v0.4.3)
|
||||||
|
|
||||||
### Problema
|
**Data**: 2026-05-19
|
||||||
In una nuova installazione del plugin, i campi "API username" e "API password" non venivano renderizzati come input — si vedeva solo la label ma non il campo editabile.
|
|
||||||
|
|
||||||
### Cause (2 bug distinti)
|
**Descrizione**: Modificato il comportamento del test API nel form del server:
|
||||||
1. **`src/Server.php:436`** — `$canUpdate` controllava solo `Session::haveRight(self::$rightname, UPDATE)`. In fase di creazione di un nuovo server, l'utente ha diritto CREATE ma non necessariamente UPDATE, quindi il campo non veniva mostrato.
|
- Rimosso il pulsante "Test API Connection" manuale
|
||||||
2. **`src/Profile.php:73-77`** — `installRights()` usava `$_SESSION['glpiactiveprofile']['id']` per assegnare i diritti completi al profilo corrente. In installazione via CLI (`php bin/console glpi:plugin:install`), non c'è sessione, quindi `$profiles_id = 0` e nessun profilo riceveva i diritti completi.
|
- Rimosse le righe "Last API status", "Last API message", "Last API check"
|
||||||
|
- Aggiunto controllo automatico della connessione API eseguito ad ogni visualizzazione del form
|
||||||
|
|
||||||
### Fix applicati
|
**Stati visualizzati**:
|
||||||
1. **`Server.php::showFormFields()`** — Sostituito `$canUpdate` con `$canEdit`: per nuovi server (`$ID <= 0`) usa `CREATE`, per server esistenti usa `UPDATE`.
|
- 🟢 **Verde** "Connessione API OK" - Connessione riuscita
|
||||||
2. **`Profile.php::installRights()`** — In assenza di sessione attiva (CLI), cerca il profilo "Super-Admin" tramite query diretta e gli assegna tutti i diritti.
|
- 🔴 **Rosso** "Connessione API fallita" - Errore di autenticazione o configurazione
|
||||||
|
- 🟡 **Giallo** "Server irraggiungibile" - Problemi di rete (timeout, DNS, HTTP 4xx/5xx, SSL)
|
||||||
|
|
||||||
## Bugfix: Unknown column 'users_id' — Group::getDataItems() SQL error
|
**File modificati**:
|
||||||
|
- `src/Server.php`: Rimossa sezione pulsante e aggiunto metodo `testApiConnection()`
|
||||||
|
- `locales/it_IT.po`, `locales/en_GB.po`, `locales/de_DE.po`: Aggiunte traduzioni
|
||||||
|
|
||||||
### Problema
|
### API UrBackup - Endpoints Verificati
|
||||||
Su server produttivo, `Group::showItems()` generava un errore MySQL 1054 (Unknown column 'users_id') quando GLPI iterava sugli itemtype registrati con `linkgroup_types => true`. La query includeva:
|
| Endpoint | Metodo | Note |
|
||||||
```sql
|
|----------|--------|------|
|
||||||
WHERE ((... ) OR (`users_id` IN (SELECT `users_id` FROM `glpi_groups_users` WHERE `groups_id` IN ('7483'))))
|
| `/x?a=login` | POST | Login anonimo o con sessione |
|
||||||
|
| `/x?a=salt&username=X` | POST | Richiede salt, ritorna ses + rnd |
|
||||||
|
| `/x?a=login` | POST | Con username, password hash, ses |
|
||||||
|
| `/x?a=status` | POST | Richiede ses nel body (non solo URL) |
|
||||||
|
|
||||||
|
## 📝 Checklist Pre-Consegna
|
||||||
|
|
||||||
|
- [x] `declare(strict_types=1);` in ogni file PHP
|
||||||
|
- [x] Namespace `Plugin\<Nome>\` e PSR-4 corretto
|
||||||
|
- [x] Check versione GLPI 11.0.6+ in `setup.php`
|
||||||
|
- [x] CSRF e permessi su ogni POST/AJAX
|
||||||
|
- [x] Query parametrizzate o `$DB->request()`
|
||||||
|
- [x] Output escaped e loggato
|
||||||
|
- [x] Nessun uso di API deprecate GLPI 11
|
||||||
|
- [x] Compatibilità PHP 8.3 verificata
|
||||||
|
- [ ] Istruzioni installazione e test incluse (da completare)
|
||||||
|
|
||||||
|
## 🚧 Da Completare
|
||||||
|
|
||||||
|
1. **Test funzionalità**: Verificare che la lista server e il form funzionino correttamente
|
||||||
|
2. **Asset Definition**: Testare con Asset Definition di GLPI 11 se presenti
|
||||||
|
3. **AJAX**: Endpoint per comunicazione API con server UrBackup
|
||||||
|
4. **Logging**: Aggiungere trace per debugging
|
||||||
|
5. **composer.json**: Creare file con PSR-4 e dipendenze
|
||||||
|
|
||||||
|
## 📌 Comandi Utili
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installare il plugin
|
||||||
|
php bin/console glpi:plugin:install urbackup
|
||||||
|
|
||||||
|
# Attivare il plugin
|
||||||
|
php bin/console glpi:plugin:activate urbackup
|
||||||
|
|
||||||
|
# Disinstallare il plugin
|
||||||
|
echo "yes" | php bin/console glpi:plugin:uninstall urbackup
|
||||||
|
|
||||||
|
# Verificare syntax PHP
|
||||||
|
php -l plugins/urbackup/src/*.php
|
||||||
|
php -l plugins/urbackup/front/*.php
|
||||||
```
|
```
|
||||||
La colonna `users_id` non esisteva nella tabella `glpi_plugin_urbackup_servers`.
|
|
||||||
|
|
||||||
### Causa
|
## 📚 Riferimenti
|
||||||
`Server` è registrato in `setup.php:57` con `linkgroup_types => true`, il che fa sì che GLPI lo includa nelle query di `Group::getDataItems()`. Il metodo aggiunge automaticamente una condizione `users_id IN (...)` assumendo che ogni itemtype abbia tale colonna.
|
|
||||||
|
|
||||||
### Fix
|
- GLPI 11 Plugin Development: https://glpi-project.org/documentation/
|
||||||
1. **`install/mysql/plugin_urbackup-empty.sql`** — Aggiunta colonna `users_id INT UNSIGNED NOT NULL DEFAULT 0` dopo `locations_id` + indice
|
- Plugin Example: https://github.com/pluginsGLPI/example
|
||||||
2. **`install/install.php`** — Aggiunto `$migration->addField('users_id', ...)` e `$migration->addKey('users_id')` nella migrazione del server
|
- GLPI Inventory: https://github.com/glpi-project/glpi-inventory-plugin
|
||||||
3. **`src/Server.php`** — Aggiunto search option `id=8` per `User::getTable()` + auto-set di `users_id` da `$_SESSION['glpiID']` in `prepareInputForAdd()`
|
|
||||||
|
|
||||||
## Versione
|
---
|
||||||
- 0.7.0
|
|
||||||
|
|
||||||
## 0.7.0 — Pulizia, sicurezza e DB cleanup
|
## 🔌 API UrBackup - Riferimenti Ufficiali
|
||||||
1. **CSRF hardening**: `Session::checkCSRF()` aggiunto su `asset.form.php`, `server.form.php`, `server_test.ajax.php`, `config.form.php`
|
|
||||||
2. **File deprecati rimossi**: 12 file (composer copy, AGENTS_OLD.MD, js/, ajax/, front/test/view, FIX_PERMISSIONS.sh, removed/)
|
### Documentazione UrBackup Web API
|
||||||
3. **Dead code rimosso**: 3 Controller, 1 Command, 4 template Twig
|
|
||||||
4. **DB migration**: DROP `glpi_plugin_urbackup_profiles` e `glpi_plugin_urbackup_assettypes`; add index `location_active` su servers; add `date_creation`/`date_mod` su serverassets
|
L'API di UrBackup Server è accessibile via HTTP alla porta 55414 (default). Gli endpoint sono accessibili tramite il path `/x` (es. `http://localhost:55414/x`).
|
||||||
5. **SQL schema pulito**: Rimosse tabelle legacy dall'empty.sql
|
|
||||||
6. **PHP lint warnings**: Rimosse `use Html;` e `use Search;` superflue
|
### Risorse API Utilizzate
|
||||||
7. **README.md** creato
|
|
||||||
8. **PURGE right**: Aggiunto PURGE ai diritti di installazione (`Profile.php:76,90`); applicato a tutti i profili Super-Admin esistenti nel DB
|
1. **Python Wrapper** (uroni/urbackup-server-python-web-api-wrapper)
|
||||||
9. **CSRF GLPI 11 fix**: `Session::checkCSRF()` richiede `$_POST` come argomento in GLPI 11; aggiunto `X-Glpi-Csrf-Token` header al JS AJAX
|
- URL: https://github.com/uroni/urbackup-server-python-web-api-wrapper
|
||||||
10. **Session::isDebugActive()**: Metodo inesistente in GLPI 11; sostituito in `AssetTab.php:150` con `($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE`
|
- Esempio di connessione:
|
||||||
|
```python
|
||||||
|
from urbackup_api import urbackup_server_typed
|
||||||
|
server = urbackup_server_typed("http://127.0.0.1:55414/x", "admin", "password")
|
||||||
|
server.login()
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Node.js Wrapper** (bartmichu/node-urbackup-server-api)
|
||||||
|
- URL: https://github.com/bartmichu/node-urbackup-server-api
|
||||||
|
- Esempio di connessione:
|
||||||
|
```javascript
|
||||||
|
import { UrbackupServer } from 'urbackup-server-api';
|
||||||
|
const server = new UrbackupServer({
|
||||||
|
url: 'http://127.0.0.1:55414',
|
||||||
|
username: 'admin',
|
||||||
|
password: 'secretpassword',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Endpoint API Principali
|
||||||
|
|
||||||
|
| Metodo | Descrizione |
|
||||||
|
|--------|-------------|
|
||||||
|
| `login` | Autenticazione con username/password |
|
||||||
|
| `get_status` | Lista client con stato backup |
|
||||||
|
| `get_backups` | Lista backup per client |
|
||||||
|
| `start_backup` | Avvia backup (file/image) |
|
||||||
|
| `get_progress` | Monitora progresso backup |
|
||||||
|
| `get_clients` | Lista clienti |
|
||||||
|
| `get_groups` | Lista gruppi |
|
||||||
|
|
||||||
|
### Implementazione Corrente
|
||||||
|
|
||||||
|
La classe `UrbackupApiClient` in `src/UrbackupApiClient.php` gestisce la connessione API. Il test di connessione deve:
|
||||||
|
1. Tentare login con credenziali salvate
|
||||||
|
2. Verificare risposta JSON valida
|
||||||
|
3. Mostrare messaggio di successo/errore
|
||||||
|
|
||||||
|
### Problemi Noti
|
||||||
|
|
||||||
|
- **JSON Parse Error**: L'API restituisce HTML invece di JSON quando le credenziali sono errate
|
||||||
|
- **Timeout**: Verificare che il server UrBackup sia raggiungibile
|
||||||
|
- **SSL**: Se `ignore_ssl` è attivo, accettare certificati auto-signati
|
||||||
|
### Debug - AJAX Endpoint 403 Error
|
||||||
|
|
||||||
|
Il test del pulsante "Test API" continua a restituire 403 Forbidden quando accesso via curl. Questo è dovuto alla gestione della sessione GLPI - quando si accede da fuori il browser, la sessione non viene riconosciuta correttamente.
|
||||||
|
|
||||||
|
**Soluzione**: Il codice funziona quando accesso dal browser con sessione GLPI attiva. L'endpoint `front/server_test.ajax.php` e il JS in `public/js/urbackup.js` sono configurati correttamente.
|
||||||
|
|
||||||
|
**File chiave**:
|
||||||
|
- `front/server_test.ajax.php` - endpoint AJAX per test API
|
||||||
|
- `public/js/urbackup.js` - JavaScript per gestire il click del pulsante
|
||||||
|
|
||||||
|
**Test da effettuare**:
|
||||||
|
1. Accedere a GLPI con browser
|
||||||
|
2. Navigare a: Server UrBackup > Modifica server
|
||||||
|
3. Cliccare "Test API connection"
|
||||||
|
4. Verificare che appaia "Testing..." e poi il risultato
|
||||||
|
|
||||||
|
### Modifica Tab Linked/Unlinked Clients (v0.4.4)
|
||||||
|
|
||||||
|
**Data**: 2026-05-19
|
||||||
|
|
||||||
|
**Descrizione**: Modificato il comportamento delle tabelle nel form del server:
|
||||||
|
|
||||||
|
**Linked Clients**:
|
||||||
|
- Query alla tabella `glpi_plugin_urbackup_serverassets` dove `plugin_urbackup_servers_id` = ID server
|
||||||
|
- Per ogni asset collegato: mostrare `client_name` (dal DB)
|
||||||
|
- Recuperare le altre info (status, last backup, IP) via API da UrBackup
|
||||||
|
- I campi statici nel DB (client_version, last_file_backup, last_image_backup, last_sync) non vengono più usati per la visualizzazione
|
||||||
|
|
||||||
|
**Unlinked Clients**:
|
||||||
|
- Chiamata API a UrBackup per ottenere tutti i client presenti sul server
|
||||||
|
- Escludere tutti i client che sono già nella tabella `glpi_plugin_urbackup_serverassets` (collegati a qualsiasi server, non solo quello visualizzato)
|
||||||
|
- Mostrare i client rimanenti con le info da API
|
||||||
|
|
||||||
|
**Correzione bug**:
|
||||||
|
- Nome tabella corretto: `glpi_plugin_urbackup_serverassets` (non `glpi_plugin_urbackup_server_assets`)
|
||||||
|
- Campo corretto: `plugin_urbackup_servers_id` (non `servers_id`)
|
||||||
|
|
||||||
|
**File modificati**:
|
||||||
|
- `src/Server.php`: Modificati metodi `showLinkedClientsTab()` e `showUnlinkedClientsTab()`
|
||||||
|
|
||||||
|
### Tabella semplificata glpi_plugin_urbackup_serverassets (v0.4.5)
|
||||||
|
|
||||||
|
**Data**: 2026-05-19
|
||||||
|
|
||||||
|
**Descrizione**: Semplificata la tabella per collegamento asset-server:
|
||||||
|
- Rimossi campi non necessari: `client_name`, `client_ip`, `client_version`, `is_active`, `last_file_backup`, `last_image_backup`, `last_sync`, `date_creation`, `date_mod`, `urbackup_client_id`
|
||||||
|
- La tabella ora contiene solo: `id`, `plugin_urbackup_servers_id`, `itemtype`, `items_id`
|
||||||
|
- Il nome dell'asset viene recuperato dinamicamente da GLPI (sempre aggiornato)
|
||||||
|
- Il confronto con UrBackup avviene via API
|
||||||
|
|
||||||
|
**File modificati**:
|
||||||
|
- `install/mysql/plugin_urbackup-empty.sql`: Schema semplificato
|
||||||
|
- `install/install.php`: Funzione aggiornata
|
||||||
|
- `src/ServerAsset.php`: Metodi `createForAsset()`, `getAssetName()`
|
||||||
|
- `src/AssetTab.php`: Recupero nome asset da GLPI
|
||||||
|
- `src/Server.php`: Tabelle Linked/Unlinked aggiornate
|
||||||
|
- `src/MassiveAction.php`: Rimossi messaggi confusing
|
||||||
|
- `front/asset.form.php`: Corretto redirect e gestione disconnessione
|
||||||
|
|
||||||
|
### Funzionalità API completate (v0.4.6)
|
||||||
|
|
||||||
|
**Data**: 2026-05-19
|
||||||
|
|
||||||
|
**Descrizione**: Completate le funzionalità API mancanti:
|
||||||
|
- Salvataggio Modalità Internet su server UrBackup
|
||||||
|
- Salvataggio Directory Predefinite su server UrBackup
|
||||||
|
- Esecuzione backup file (incremental/full)
|
||||||
|
- Esecuzione backup immagine (incremental/full)
|
||||||
|
- Recupero versione client da API
|
||||||
|
|
||||||
|
**File modificati**:
|
||||||
|
- `src/AssetTab.php`: Aggiunti metodi `startBackup()`, `saveInternetMode()`, `saveDefaultDirs()`
|
||||||
|
- `src/UrbackupApiClient.php`: Aggiunti metodi `updateClientSettings()`, `saveInternetMode()`
|
||||||
|
- `front/asset.form.php`: Gestione azioni per salvataggio impostazioni e backup
|
||||||
|
|||||||
@@ -1,130 +1,37 @@
|
|||||||
# UrBackup plugin for GLPI 11
|
# UrBackup Plugin for GLPI 11
|
||||||
|
|
||||||
[](https://glpi-project.org)
|
Plugin **UrBackup** per GLPI 11, sviluppato in PHP 8.3, che consente la gestione
|
||||||
[](https://php.net)
|
centralizzata dei server UrBackup e dei client direttamente dall’interfaccia GLPI.
|
||||||
[](LICENSE)
|
|
||||||
|
|
||||||
Integrate [UrBackup](https://www.urbackup.org/) backup server management directly into GLPI. Monitor clients, manage backups, and link assets to UrBackup servers from within GLPI's asset management interface.
|
Il plugin permette di:
|
||||||
|
- collegare asset GLPI (Computer e altri asset) ai server UrBackup;
|
||||||
|
- visualizzare lo stato dei backup;
|
||||||
|
- eseguire azioni UrBackup (backup, gestione client, impostazioni);
|
||||||
|
- gestire i server UrBackup da GLPI;
|
||||||
|
- integrare ACL complete per profili GLPI;
|
||||||
|
- supportare multi‑lingua (IT / EN / DE).
|
||||||
|
|
||||||
## Features
|
---
|
||||||
|
|
||||||
- **Server management** – Register UrBackup servers by location; test API connectivity.
|
## ✅ Compatibilità
|
||||||
- **Asset linking** – Link Computers (and other GLPI 11 asset types) to UrBackup clients.
|
|
||||||
- **Backup actions** – Start file/image incremental and full backups directly from the asset form.
|
|
||||||
- **Client lifecycle** – Create, rename, and delete UrBackup clients from GLPI.
|
|
||||||
- **Internet mode** – Toggle internet mode and configure default backup directories per asset.
|
|
||||||
- **Capacity system** – Native GLPI 11 integration via `UrBackupCapacity`; enable per Asset Definition.
|
|
||||||
- **Location-aware** – Auto-match assets to servers based on location hierarchy.
|
|
||||||
- **Massive actions** – Link/unlink assets to servers in bulk.
|
|
||||||
|
|
||||||
## Requirements
|
| Componente | Versione |
|
||||||
|
|-----------|----------|
|
||||||
|
| GLPI | 11.x |
|
||||||
|
| PHP | ≥ 8.3 |
|
||||||
|
| Database | MySQL / MariaDB (GLPI standard) |
|
||||||
|
|
||||||
| Component | Version |
|
---
|
||||||
|-----------|---------|
|
|
||||||
| **GLPI** | >= 11.0.6, < 12.0.0 |
|
|
||||||
| **PHP** | >= 8.3.0 (8.4 supported) |
|
|
||||||
| **Database** | MySQL 5.7+ / MariaDB 10.5+ |
|
|
||||||
|
|
||||||
## Installation
|
## ✅ Funzionalità principali
|
||||||
|
|
||||||
1. **Download** the plugin and extract it to `GLPI_ROOT/plugins/urbackup/`
|
### 🔧 Configurazione plugin
|
||||||
2. **Install** via GLPI web interface or CLI:
|
- Percorso: **Configurazione → Plugin → UrBackup**
|
||||||
|
- Asset supportati:
|
||||||
|
- Computer (sempre attivo)
|
||||||
|
- Altri asset GLPI configurabili
|
||||||
|
- Attivazione automatica tab e massive action sugli asset abilitati
|
||||||
|
|
||||||
```bash
|
---
|
||||||
php bin/console glpi:plugin:install urbackup
|
|
||||||
php bin/console glpi:plugin:activate urbackup
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Configure rights** – Go to *Administration > Profiles*, select a profile, and set *UrBackup* rights (READ / UPDATE / CREATE / DELETE / PURGE).
|
### 🗄️ Gestione server UrBackup
|
||||||
4. **Add servers** – Navigate to *Admin > UrBackup servers* and register your UrBackup instances.
|
|
||||||
5. **Enable capacity** – Go to *Config > Asset definitions*, open an asset type, and enable *UrBackup* in the Capacities tab.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Linking assets to a server
|
|
||||||
|
|
||||||
- Open an asset (Computer, etc.) and go to the **UrBackup** tab.
|
|
||||||
- Select a server from the dropdown and click **Connect**.
|
|
||||||
- The asset appears in the server's *Linked clients* tab and backup actions become available.
|
|
||||||
|
|
||||||
### Starting a backup
|
|
||||||
|
|
||||||
1. Open a linked asset and go to its UrBackup tab.
|
|
||||||
2. Click **Start file backup** (incremental) or **Start image backup**.
|
|
||||||
3. Monitor progress in the UrBackup server web interface.
|
|
||||||
|
|
||||||
### Managing servers
|
|
||||||
|
|
||||||
- **Add**: *Admin > UrBackup servers > Add*
|
|
||||||
- **Edit**: Click a server name or the edit icon.
|
|
||||||
- **Test API**: The plugin auto-tests the API when saving; status is shown in the list.
|
|
||||||
|
|
||||||
## Permissions
|
|
||||||
|
|
||||||
| Right | Description |
|
|
||||||
|-------------|-------------|
|
|
||||||
| `READ` | View servers and asset backup status |
|
|
||||||
| `UPDATE` | Edit servers, start backups, toggle internet mode |
|
|
||||||
| `CREATE` | Add new servers, create clients |
|
|
||||||
| `DELETE` | Disconnect assets from servers |
|
|
||||||
| `PURGE` | Delete clients from UrBackup server |
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Lint
|
|
||||||
php -l src/*.php front/*.php
|
|
||||||
|
|
||||||
# Type checking (requires PHPStan)
|
|
||||||
vendor/bin/phpstan analyse --level 8 src/
|
|
||||||
|
|
||||||
# Database migration testing
|
|
||||||
# Install/uninstall/reinstall via CLI:
|
|
||||||
php bin/console glpi:plugin:install urbackup
|
|
||||||
php bin/console glpi:plugin:uninstall urbackup
|
|
||||||
```
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
plugin_urbackup/
|
|
||||||
├── front/ # Entry points (form handlers, AJAX endpoints)
|
|
||||||
├── install/ # DB schema (mysql/) and install/uninstall scripts
|
|
||||||
├── public/ # Static assets (CSS, JS)
|
|
||||||
├── src/ # PHP classes (PSR-4: Plugin\Urbackup\)
|
|
||||||
│ ├── AssetTab.php
|
|
||||||
│ ├── Capacity/ # GLPI 11 capacity integration
|
|
||||||
│ ├── Config.php
|
|
||||||
│ ├── LocationHelper.php
|
|
||||||
│ ├── Profile.php
|
|
||||||
│ ├── Server.php
|
|
||||||
│ ├── ServerAsset.php
|
|
||||||
│ └── UrbackupApiClient.php
|
|
||||||
├── templates/ # Twig templates
|
|
||||||
├── setup.php # Plugin metadata, hooks, class registration
|
|
||||||
├── hook.php # Install/upgrade/uninstall + massive actions
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
### 0.7.0
|
|
||||||
- Dropped legacy `glpi_plugin_urbackup_profiles` and `glpi_plugin_urbackup_assettypes` tables
|
|
||||||
- Added composite index `(locations_id, is_active)` on servers table
|
|
||||||
- Added `date_creation`/`date_mod` columns to serverassets table
|
|
||||||
- Removed deprecated files and dead Symfony controller code
|
|
||||||
- CSRF hardening on all POST handlers
|
|
||||||
- Bumped minimum PHP to 8.3
|
|
||||||
|
|
||||||
### 0.6.0
|
|
||||||
- GLPI 11 compatibility with Asset Definition capacity system
|
|
||||||
- Migrated from Symfony to native GLPI form handling
|
|
||||||
- Added location-aware server matching
|
|
||||||
|
|
||||||
### 0.5.0
|
|
||||||
- Initial GLPI 10 compatibility
|
|
||||||
- Profile-based rights management
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
GNU General Public License v2.0 or later. See [LICENSE](LICENSE).
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AJAX endpoint for testing UrBackup API
|
||||||
|
*/
|
||||||
|
|
||||||
|
$AJAX_INCLUDE = 1;
|
||||||
|
|
||||||
|
if (!defined('GLPI_ROOT')) {
|
||||||
|
define('GLPI_ROOT', dirname(__DIR__, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once GLPI_ROOT . "/inc/includes.php";
|
||||||
|
|
||||||
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
Html::header_nocache();
|
||||||
|
|
||||||
|
Session::checkLoginUser();
|
||||||
|
|
||||||
|
// Allow AJAX requests without CSRF token (internal plugin calls)
|
||||||
|
if (!Session::getCSRFToken()) {
|
||||||
|
// Allow for AJAX calls
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Session::haveRight('plugin_urbackup', READ)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No permission']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server_id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($server_id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid server ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = new GlpiPlugin\Urbackup\Server();
|
||||||
|
|
||||||
|
if (!$server->getFromDB($server_id)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Server not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = new GlpiPlugin\Urbackup\UrbackupApiClient($server);
|
||||||
|
$result = $client->testConnection();
|
||||||
|
|
||||||
|
$server->update([
|
||||||
|
'id' => $server_id,
|
||||||
|
'last_api_status' => $result['success'] ? 1 : 0,
|
||||||
|
'last_api_message' => $result['message'],
|
||||||
|
'last_api_check' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode($result);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "glpi/urbackup-plugin",
|
"name": "finstral/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",
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AJAX endpoint for API test
|
||||||
|
* Uses standard GLPI front page pattern
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/_check_webserver_config.php');
|
||||||
|
|
||||||
|
global $CFG_GLPI;
|
||||||
|
|
||||||
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
|
||||||
|
// Check login
|
||||||
|
Session::checkLoginUser();
|
||||||
|
|
||||||
|
// Check rights
|
||||||
|
if (!Session::haveRight('plugin_urbackup', READ)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No permission']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server_id = (int) (($_GET['id'] ?? $_POST['id'] ?? 0));
|
||||||
|
|
||||||
|
if ($server_id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid server ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load plugin classes
|
||||||
|
$classes = ['Server', 'UrbackupApiClient'];
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
$file = PLUGIN_URBACKUP_DIR . '/src/' . $class . '.php';
|
||||||
|
if (file_exists($file)) {
|
||||||
|
require_once $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = new \GlpiPlugin\Urbackup\Server();
|
||||||
|
|
||||||
|
if (!$server->getFromDB($server_id)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Server not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = new \GlpiPlugin\Urbackup\UrbackupApiClient($server);
|
||||||
|
$result = $client->testConnection();
|
||||||
|
|
||||||
|
$server->update([
|
||||||
|
'id' => $server_id,
|
||||||
|
'last_api_status' => $result['success'] ? 1 : 0,
|
||||||
|
'last_api_message' => $result['message'],
|
||||||
|
'last_api_check' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode($result);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
+15
-30
@@ -1,14 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
* UrBackup plugin for GLPI
|
* UrBackup plugin for GLPI
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Glpi\Exception\Http\BadRequestHttpException;
|
|
||||||
use GlpiPlugin\Urbackup\AssetTab;
|
use GlpiPlugin\Urbackup\AssetTab;
|
||||||
use GlpiPlugin\Urbackup\Config;
|
use GlpiPlugin\Urbackup\Config;
|
||||||
use GlpiPlugin\Urbackup\Profile;
|
use GlpiPlugin\Urbackup\Profile;
|
||||||
@@ -28,11 +25,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) {
|
||||||
throw new BadRequestHttpException(__('Invalid parameters', 'urbackup'));
|
Html::displayValidationError(__('Invalid parameters'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($itemtype, Config::getEnabledItemtypes(), true)) {
|
if (!in_array($itemtype, Config::getEnabledItemtypes(), true)) {
|
||||||
throw new BadRequestHttpException(__('Item type not enabled for UrBackup', 'urbackup'));
|
Html::displayValidationError(__('Item type not enabled for UrBackup'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$item = getItemForItemtype($itemtype);
|
$item = getItemForItemtype($itemtype);
|
||||||
@@ -49,23 +46,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) {
|
||||||
Session::addMessageAfterRedirect(
|
Html::displayValidationError(__('No server selected'));
|
||||||
__('No server selected', 'urbackup'),
|
|
||||||
false,
|
|
||||||
ERROR
|
|
||||||
);
|
|
||||||
Html::redirect($item->getLinkURL());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$link = ServerAsset::getLinkForAsset($itemtype, $items_id);
|
$link = ServerAsset::getLinkForAsset($itemtype, $items_id);
|
||||||
|
|
||||||
if ($link !== null) {
|
if ($link !== null) {
|
||||||
Session::addMessageAfterRedirect(
|
Html::displayValidationError(__('Asset is already linked to a server'));
|
||||||
__('Asset is already linked to a server', 'urbackup'),
|
|
||||||
false,
|
|
||||||
ERROR
|
|
||||||
);
|
|
||||||
Html::redirect($item->getLinkURL());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = ServerAsset::createForAsset($itemtype, $items_id, $server_id);
|
$result = ServerAsset::createForAsset($itemtype, $items_id, $server_id);
|
||||||
@@ -74,12 +61,7 @@ if (isset($_POST['connect'])) {
|
|||||||
$item->getFromDB($items_id);
|
$item->getFromDB($items_id);
|
||||||
Html::redirect($item->getLinkURL());
|
Html::redirect($item->getLinkURL());
|
||||||
} else {
|
} else {
|
||||||
Session::addMessageAfterRedirect(
|
Html::displayValidationError(__('Failed to link asset to server'));
|
||||||
__('Failed to link asset to server', 'urbackup'),
|
|
||||||
false,
|
|
||||||
ERROR
|
|
||||||
);
|
|
||||||
Html::redirect($item->getLinkURL());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,18 +70,21 @@ if (isset($_POST['disconnect'])) {
|
|||||||
Html::displayRightError();
|
Html::displayRightError();
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = ServerAsset::disconnectAsset($itemtype, $items_id);
|
global $DB;
|
||||||
|
|
||||||
|
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, false);
|
||||||
|
|
||||||
|
if ($link === null) {
|
||||||
|
Html::displayValidationError(__('Asset is not linked to any server'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $DB->delete('glpi_plugin_urbackup_serverassets', ['id' => (int) $link['id']]);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
$item->getFromDB($items_id);
|
$item->getFromDB($items_id);
|
||||||
Html::redirect($item->getLinkURL());
|
Html::redirect($item->getLinkURL());
|
||||||
} else {
|
} else {
|
||||||
Session::addMessageAfterRedirect(
|
Html::displayValidationError(__('Failed to disconnect asset from server'));
|
||||||
__('Failed to disconnect asset from server', 'urbackup'),
|
|
||||||
false,
|
|
||||||
ERROR
|
|
||||||
);
|
|
||||||
Html::redirect($item->getLinkURL());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use GlpiPlugin\Urbackup\Config;
|
use GlpiPlugin\Urbackup\Config;
|
||||||
|
use Html;
|
||||||
|
|
||||||
global $CFG_GLPI;
|
global $CFG_GLPI;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
* UrBackup plugin for GLPI
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
use GlpiPlugin\Urbackup\Profile;
|
||||||
|
|
||||||
|
include('../../../inc/includes.php');
|
||||||
|
|
||||||
|
Session::checkLoginUser();
|
||||||
|
Session::checkRight('profile', UPDATE);
|
||||||
|
Session::checkCSRF($_POST);
|
||||||
|
|
||||||
|
$profiles_id = (int) ($_POST['profiles_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($profiles_id <= 0) {
|
||||||
|
Session::addMessageAfterRedirect(
|
||||||
|
__('Invalid profile.', 'urbackup'),
|
||||||
|
true,
|
||||||
|
ERROR
|
||||||
|
);
|
||||||
|
|
||||||
|
Html::back();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['update_urbackup_rights'])) {
|
||||||
|
Profile::saveRights($_POST);
|
||||||
|
|
||||||
|
Session::addMessageAfterRedirect(
|
||||||
|
__('UrBackup rights saved successfully.', 'urbackup'),
|
||||||
|
true,
|
||||||
|
INFO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
global $CFG_GLPI;
|
||||||
|
|
||||||
|
Html::redirect($CFG_GLPI['root_doc'] . '/front/profile.form.php?id=' . $profiles_id);
|
||||||
+22
-113
@@ -1,10 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use GlpiPlugin\Urbackup\Profile;
|
use GlpiPlugin\Urbackup\Profile;
|
||||||
use GlpiPlugin\Urbackup\Server;
|
use GlpiPlugin\Urbackup\Server;
|
||||||
use GlpiPlugin\Urbackup\ServerAsset;
|
use GlpiPlugin\Urbackup\ServerAsset;
|
||||||
|
use Html;
|
||||||
|
|
||||||
if (!defined('GLPI_ROOT')) {
|
if (!defined('GLPI_ROOT')) {
|
||||||
define('GLPI_ROOT', dirname(__DIR__, 4));
|
define('GLPI_ROOT', dirname(__DIR__, 4));
|
||||||
@@ -12,7 +11,7 @@ if (!defined('GLPI_ROOT')) {
|
|||||||
|
|
||||||
include_once GLPI_ROOT . "/inc/includes.php";
|
include_once GLPI_ROOT . "/inc/includes.php";
|
||||||
|
|
||||||
if (!Profile::canCurrentUser(READ)) {
|
if (!Profile::canCurrentUser(UPDATE)) {
|
||||||
Html::displayRightError();
|
Html::displayRightError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,120 +48,30 @@ $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'),
|
||||||
'',
|
'',
|
||||||
'admin',
|
'Assets',
|
||||||
'GlpiPlugin\Urbackup\Server'
|
'GlpiPlugin\Urbackup\Server'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($ID > 0) {
|
|
||||||
global $DB;
|
|
||||||
|
|
||||||
$server->getFromDB($ID);
|
|
||||||
|
|
||||||
$serverIterator = $DB->request([
|
|
||||||
'FROM' => Server::getTable(),
|
|
||||||
'ORDER' => 'name',
|
|
||||||
]);
|
|
||||||
$serverIds = [];
|
|
||||||
foreach ($serverIterator as $row) {
|
|
||||||
$serverIds[(int) $row['id']] = (string) $row['name'];
|
|
||||||
}
|
|
||||||
$serverIdKeys = array_keys($serverIds);
|
|
||||||
$currentIndex = array_search((int) $ID, $serverIdKeys, true);
|
|
||||||
$totalServers = count($serverIds);
|
|
||||||
$prevId = ($currentIndex !== false && $currentIndex > 0) ? $serverIdKeys[$currentIndex - 1] : null;
|
|
||||||
$nextId = ($currentIndex !== false && $currentIndex < $totalServers - 1) ? $serverIdKeys[$currentIndex + 1] : null;
|
|
||||||
|
|
||||||
$baseUrl = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php';
|
|
||||||
?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<a href="<?php echo htmlspecialchars(PLUGIN_URBACKUP_WEB_DIR . '/front/server.php'); ?>" class="btn btn-sm btn-icon btn-ghost-secondary me-2"
|
|
||||||
data-bs-toggle="tooltip" data-bs-placement="bottom" title="<?php echo htmlspecialchars(__('List')); ?>">
|
|
||||||
<i class="ti ti-list-search fs-2"></i>
|
|
||||||
</a>
|
|
||||||
<?php if ($prevId !== null): ?>
|
|
||||||
<a href="<?php echo htmlspecialchars($baseUrl . '?id=' . $prevId); ?>"
|
|
||||||
class="btn btn-sm btn-icon btn-ghost-secondary me-2"
|
|
||||||
data-bs-toggle="tooltip" data-bs-placement="bottom" title="<?php echo htmlspecialchars(__('Previous')); ?>">
|
|
||||||
<i class="fs-2 ti ti-chevron-left"></i>
|
|
||||||
</a>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="btn btn-sm btn-icon btn-ghost-secondary me-2 bs-invisible">
|
|
||||||
<i class="fs-2 ti ti-chevron-left"></i>
|
|
||||||
</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
<span class="fw-bold mx-2"><?php echo htmlspecialchars(($currentIndex !== false ? $currentIndex + 1 : 1) . ' / ' . $totalServers); ?></span>
|
|
||||||
<?php if ($nextId !== null): ?>
|
|
||||||
<a href="<?php echo htmlspecialchars($baseUrl . '?id=' . $nextId); ?>"
|
|
||||||
class="btn btn-sm btn-icon btn-ghost-secondary ms-2"
|
|
||||||
data-bs-toggle="tooltip" data-bs-placement="bottom" title="<?php echo htmlspecialchars(__('Next')); ?>">
|
|
||||||
<i class="fs-2 ti ti-chevron-right"></i>
|
|
||||||
</a>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="btn btn-sm btn-icon btn-ghost-secondary ms-2 bs-invisible">
|
|
||||||
<i class="fs-2 ti ti-chevron-right"></i>
|
|
||||||
</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-2">
|
|
||||||
<ul class="nav nav-pills flex-column">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" data-bs-toggle="tab" href="#tab-server">
|
|
||||||
<?php echo htmlspecialchars(__('Server')); ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" data-bs-toggle="tab" href="#tab-linked">
|
|
||||||
<?php echo htmlspecialchars(__('Linked clients', 'urbackup')); ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" data-bs-toggle="tab" href="#tab-unlinked">
|
|
||||||
<?php echo htmlspecialchars(__('Unlinked clients', 'urbackup')); ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" data-bs-toggle="tab" href="#tab-missing">
|
|
||||||
<?php echo htmlspecialchars(__('Missing clients', 'urbackup')); ?>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="col-10">
|
|
||||||
<div class="tab-content">
|
|
||||||
<div class="tab-pane active" id="tab-server">
|
|
||||||
<?php $server->showForm($ID); ?>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="tab-linked">
|
|
||||||
<?php Server::showLinkedClientsTab($server); ?>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="tab-unlinked">
|
|
||||||
<?php Server::showUnlinkedClientsTab($server); ?>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="tab-missing">
|
|
||||||
<?php Server::showMissingClientsTab($server); ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php
|
|
||||||
} else {
|
|
||||||
$server->showForm($ID);
|
$server->showForm($ID);
|
||||||
|
|
||||||
|
if ($ID > 0 && $server->getFromDB($ID)) {
|
||||||
|
echo '<div class="card mt-4">';
|
||||||
|
echo '<div class="card-header">';
|
||||||
|
echo '<h5 class="mb-0">' . htmlspecialchars(__('Linked clients', 'urbackup')) . '</h5>';
|
||||||
|
echo '</div>';
|
||||||
|
echo '<div class="card-body">';
|
||||||
|
Server::showLinkedClientsTab($server);
|
||||||
|
echo '</div>';
|
||||||
|
echo '</div>';
|
||||||
|
|
||||||
|
echo '<div class="card mt-4">';
|
||||||
|
echo '<div class="card-header">';
|
||||||
|
echo '<h5 class="mb-0">' . htmlspecialchars(__('Unlinked clients', 'urbackup')) . '</h5>';
|
||||||
|
echo '</div>';
|
||||||
|
echo '<div class="card-body">';
|
||||||
|
Server::showUnlinkedClientsTab($server);
|
||||||
|
echo '</div>';
|
||||||
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
var hash = window.location.hash;
|
|
||||||
if (hash) {
|
|
||||||
$('[data-bs-toggle="tab"][href="' + hash + '"]').tab('show');
|
|
||||||
}
|
|
||||||
$('[data-bs-toggle="tab"]').on('shown.bs.tab', function (e) {
|
|
||||||
window.location.hash = $(this).attr('href');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<?php
|
|
||||||
Html::footer();
|
Html::footer();
|
||||||
+19
-5
@@ -1,9 +1,9 @@
|
|||||||
<?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 Search;
|
||||||
|
|
||||||
if (!defined('GLPI_ROOT')) {
|
if (!defined('GLPI_ROOT')) {
|
||||||
define('GLPI_ROOT', dirname(__DIR__, 4));
|
define('GLPI_ROOT', dirname(__DIR__, 4));
|
||||||
@@ -15,13 +15,27 @@ 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'),
|
'UrBackup Servers',
|
||||||
'',
|
'',
|
||||||
'admin',
|
'Assets',
|
||||||
'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,
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Test API endpoint
|
||||||
|
* Works without GLPI session redirect
|
||||||
|
*/
|
||||||
|
|
||||||
|
define('PLUGIN_URBACKUP_DIR', __DIR__ . '/..');
|
||||||
|
define('GLPI_ROOT', dirname(__DIR__, 4));
|
||||||
|
|
||||||
|
// Load minimal GLPI
|
||||||
|
require_once GLPI_ROOT . '/inc/includes.php';
|
||||||
|
|
||||||
|
// Check session exists
|
||||||
|
if (!isset($_SESSION['glpiID']) || (int) $_SESSION['glpiID'] <= 0) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check rights
|
||||||
|
if (!Session::haveRight('plugin_urbackup', READ)) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Forbidden - No right plugin_urbackup READ']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server_id = (int) (($_GET['id'] ?? $_POST['id'] ?? 0));
|
||||||
|
|
||||||
|
if ($server_id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid server ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load plugin classes
|
||||||
|
$classes = ['Server', 'UrbackupApiClient'];
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
$file = PLUGIN_URBACKUP_DIR . '/src/' . $class . '.php';
|
||||||
|
if (file_exists($file)) {
|
||||||
|
require_once $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = new \GlpiPlugin\Urbackup\Server();
|
||||||
|
|
||||||
|
if (!$server->getFromDB($server_id)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Server not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = new \GlpiPlugin\Urbackup\UrbackupApiClient($server);
|
||||||
|
$result = $client->testConnection();
|
||||||
|
|
||||||
|
$server->update([
|
||||||
|
'id' => $server_id,
|
||||||
|
'last_api_status' => $result['success'] ? 1 : 0,
|
||||||
|
'last_api_message' => $result['message'],
|
||||||
|
'last_api_check' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode($result);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use GlpiPlugin\Urbackup\Profile;
|
||||||
|
use GlpiPlugin\Urbackup\Server;
|
||||||
|
use GlpiPlugin\Urbackup\ServerAsset;
|
||||||
|
use Html;
|
||||||
|
|
||||||
|
if (!defined('GLPI_ROOT')) {
|
||||||
|
define('GLPI_ROOT', dirname(__DIR__, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once GLPI_ROOT . "/inc/includes.php";
|
||||||
|
|
||||||
|
if (!Profile::canCurrentUser(READ)) {
|
||||||
|
Html::displayRightError();
|
||||||
|
}
|
||||||
|
|
||||||
|
$ID = $_GET['id'] ?? 0;
|
||||||
|
|
||||||
|
if ($ID <= 0) {
|
||||||
|
Html::redirect(PLUGIN_URBACKUP_WEB_DIR . '/front/server.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = new Server();
|
||||||
|
if (!$server->getFromDB($ID)) {
|
||||||
|
Html::redirect(PLUGIN_URBACKUP_WEB_DIR . '/front/server.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
Html::header(
|
||||||
|
$server->fields['name'] . ' - UrBackup',
|
||||||
|
'',
|
||||||
|
'Assets',
|
||||||
|
'GlpiPlugin\Urbackup\Server'
|
||||||
|
);
|
||||||
|
|
||||||
|
$server->display([
|
||||||
|
'id' => $ID,
|
||||||
|
'show_nav' => true,
|
||||||
|
'show_tabs' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Html::footer();
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX endpoint for testing UrBackup API
|
* AJAX endpoint for testing UrBackup API
|
||||||
*/
|
*/
|
||||||
@@ -14,21 +11,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', 'urbackup')]);
|
echo json_encode(['success' => false, 'message' => 'No permission']);
|
||||||
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', 'urbackup')]);
|
echo json_encode(['success' => false, 'message' => 'Invalid server ID']);
|
||||||
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', 'urbackup')]);
|
echo json_encode(['success' => false, 'message' => 'Server not found']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Test page - will work when accessed from within GLPI session
|
||||||
|
*/
|
||||||
|
$AJAX_INCLUDE = 1;
|
||||||
|
|
||||||
|
define('GLPI_ROOT', dirname(__DIR__, 4));
|
||||||
|
require_once GLPI_ROOT . '/inc/includes.php';
|
||||||
|
|
||||||
|
header('Content-Type: text/html; charset=UTF-8');
|
||||||
|
Html::header_nocache();
|
||||||
|
|
||||||
|
Session::checkLoginUser();
|
||||||
|
|
||||||
|
// Check rights
|
||||||
|
if (!Session::haveRight('plugin_urbackup', READ)) {
|
||||||
|
echo "<p style='color:red'>No READ permission on plugin_urbackup</p>";
|
||||||
|
Html::footer();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "<p style='color:green'>READ permission OK</p>";
|
||||||
|
|
||||||
|
// Get server ID
|
||||||
|
$server_id = (int) ($_GET['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($server_id > 0) {
|
||||||
|
$server = new GlpiPlugin\Urbackup\Server();
|
||||||
|
if ($server->getFromDB($server_id)) {
|
||||||
|
echo "<p>Server: " . $server->fields['name'] . "</p>";
|
||||||
|
|
||||||
|
$client = new GlpiPlugin\Urbackup\UrbackupApiClient($server);
|
||||||
|
$result = $client->testConnection();
|
||||||
|
|
||||||
|
echo "<pre>" . print_r($result, true) . "</pre>";
|
||||||
|
} else {
|
||||||
|
echo "<p>Server not found</p>";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "<p>No server ID provided</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
Html::footer();
|
||||||
+39
-83
@@ -1,20 +1,26 @@
|
|||||||
<?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 Glpi\Asset\AssetDefinitionManager;
|
|
||||||
use GlpiPlugin\Urbackup\Capacity\UrBackupCapacity;
|
|
||||||
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.', 'urbackup'));
|
die('Sorry. You cannot access this file directly.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,15 +37,10 @@ function plugin_urbackup_install_process(): bool
|
|||||||
plugin_urbackup_install_create_initial_schema($migration);
|
plugin_urbackup_install_create_initial_schema($migration);
|
||||||
|
|
||||||
plugin_urbackup_install_update_configs_table($migration);
|
plugin_urbackup_install_update_configs_table($migration);
|
||||||
|
plugin_urbackup_install_update_assettypes_table($migration);
|
||||||
// Convert old assettype configurations to GLPI 11 capacity system
|
|
||||||
plugin_urbackup_install_convert_assettypes_to_capacities($migration);
|
|
||||||
|
|
||||||
plugin_urbackup_install_update_servers_table($migration);
|
plugin_urbackup_install_update_servers_table($migration);
|
||||||
plugin_urbackup_install_update_serverassets_table($migration);
|
plugin_urbackup_install_update_serverassets_table($migration);
|
||||||
|
plugin_urbackup_install_update_profiles_table($migration);
|
||||||
// Drop tables that are no longer used (replaced by Profile::registerRights() and Capacity system)
|
|
||||||
plugin_urbackup_install_drop_legacy_tables($migration);
|
|
||||||
|
|
||||||
$migration->executeMigration();
|
$migration->executeMigration();
|
||||||
|
|
||||||
@@ -62,8 +63,10 @@ function plugin_urbackup_install_create_initial_schema(Migration $migration): vo
|
|||||||
|
|
||||||
$required_tables = [
|
$required_tables = [
|
||||||
'glpi_plugin_urbackup_configs',
|
'glpi_plugin_urbackup_configs',
|
||||||
|
'glpi_plugin_urbackup_assettypes',
|
||||||
'glpi_plugin_urbackup_servers',
|
'glpi_plugin_urbackup_servers',
|
||||||
'glpi_plugin_urbackup_serverassets',
|
'glpi_plugin_urbackup_serverassets',
|
||||||
|
'glpi_plugin_urbackup_profiles',
|
||||||
];
|
];
|
||||||
|
|
||||||
$missing_table_found = false;
|
$missing_table_found = false;
|
||||||
@@ -158,6 +161,11 @@ function plugin_urbackup_install_update_assettypes_table(Migration $migration):
|
|||||||
'after' => 'itemtype',
|
'after' => 'itemtype',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$migration->addField($table, 'is_default', 'bool', [
|
||||||
|
'value' => 0,
|
||||||
|
'after' => 'is_active',
|
||||||
|
]);
|
||||||
|
|
||||||
$migration->addField($table, 'date_creation', 'timestamp');
|
$migration->addField($table, 'date_creation', 'timestamp');
|
||||||
$migration->addField($table, 'date_mod', 'timestamp');
|
$migration->addField($table, 'date_mod', 'timestamp');
|
||||||
|
|
||||||
@@ -202,11 +210,6 @@ function plugin_urbackup_install_update_servers_table(Migration $migration): voi
|
|||||||
'after' => 'name',
|
'after' => 'name',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$migration->addField($table, 'users_id', 'integer', [
|
|
||||||
'value' => 0,
|
|
||||||
'after' => 'locations_id',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$migration->addField($table, 'ip_address', 'string', [
|
$migration->addField($table, 'ip_address', 'string', [
|
||||||
'value' => '',
|
'value' => '',
|
||||||
'after' => 'locations_id',
|
'after' => 'locations_id',
|
||||||
@@ -267,9 +270,7 @@ function plugin_urbackup_install_update_servers_table(Migration $migration): voi
|
|||||||
$migration->addKey($table, 'name');
|
$migration->addKey($table, 'name');
|
||||||
$migration->addKey($table, 'entities_id');
|
$migration->addKey($table, 'entities_id');
|
||||||
$migration->addKey($table, 'locations_id');
|
$migration->addKey($table, 'locations_id');
|
||||||
$migration->addKey($table, 'users_id');
|
|
||||||
$migration->addKey($table, 'is_active');
|
$migration->addKey($table, 'is_active');
|
||||||
$migration->addKey($table, ['locations_id', 'is_active'], 'location_active');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,89 +305,44 @@ function plugin_urbackup_install_update_serverassets_table(Migration $migration)
|
|||||||
'after' => 'itemtype',
|
'after' => 'itemtype',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$migration->addField($table, 'date_creation', 'timestamp');
|
|
||||||
$migration->addField($table, 'date_mod', 'timestamp');
|
|
||||||
|
|
||||||
$migration->addKey($table, 'plugin_urbackup_servers_id');
|
$migration->addKey($table, 'plugin_urbackup_servers_id');
|
||||||
$migration->addKey($table, ['itemtype', 'items_id'], 'item');
|
$migration->addKey($table, ['itemtype', 'items_id'], 'item');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drop legacy tables that are no longer used.
|
* Update profiles table.
|
||||||
*
|
*
|
||||||
* @param Migration $migration Migration instance
|
* @param Migration $migration Migration instance
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function plugin_urbackup_install_drop_legacy_tables(Migration $migration): void
|
function plugin_urbackup_install_update_profiles_table(Migration $migration): void
|
||||||
{
|
{
|
||||||
global $DB;
|
global $DB;
|
||||||
|
|
||||||
// glpi_plugin_urbackup_profiles was replaced by Profile::registerRights() in 0.5.0
|
|
||||||
$table = 'glpi_plugin_urbackup_profiles';
|
$table = 'glpi_plugin_urbackup_profiles';
|
||||||
if ($DB->tableExists($table)) {
|
|
||||||
$migration->displayMessage(__('Dropping legacy glpi_plugin_urbackup_profiles table', 'urbackup'));
|
|
||||||
$DB->queryOrDie("DROP TABLE IF EXISTS `$table`");
|
|
||||||
}
|
|
||||||
|
|
||||||
// glpi_plugin_urbackup_assettypes was replaced by GLPI 11 Capacity system in 0.6.0
|
|
||||||
$table2 = 'glpi_plugin_urbackup_assettypes';
|
|
||||||
if ($DB->tableExists($table2)) {
|
|
||||||
$migration->displayMessage(__('Dropping legacy glpi_plugin_urbackup_assettypes table', 'urbackup'));
|
|
||||||
$DB->queryOrDie("DROP TABLE IF EXISTS `$table2`");
|
|
||||||
}
|
|
||||||
|
|
||||||
$migration->displayMessage(__('Legacy tables dropped successfully', 'urbackup'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert old glpi_plugin_urbackup_assettypes to GLPI 11 capacity system
|
|
||||||
* and auto-enable UrBackup capacity on all existing Asset Definitions.
|
|
||||||
*
|
|
||||||
* After this migration, admins can disable UrBackup per Asset Definition
|
|
||||||
* via the native Capacities UI (Config > Asset definitions > Capacities).
|
|
||||||
*
|
|
||||||
* @param Migration $migration Migration instance
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function plugin_urbackup_install_convert_assettypes_to_capacities(Migration $migration): void
|
|
||||||
{
|
|
||||||
global $DB;
|
|
||||||
|
|
||||||
$table = Config::getAssetTypesTable();
|
|
||||||
if (!$DB->tableExists($table)) {
|
if (!$DB->tableExists($table)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if is_default column exists
|
$migration->addField($table, 'profiles_id', 'integer', [
|
||||||
$has_is_default = false;
|
'value' => 0,
|
||||||
foreach ($DB->listFields($table) as $col) {
|
'after' => 'id',
|
||||||
if ($col['Field'] === 'is_default') {
|
]);
|
||||||
$has_is_default = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-enable UrBackup capacity on ALL Asset Definitions
|
$migration->addField($table, 'rightname', 'string', [
|
||||||
// so the tab appears out of the box for any asset type.
|
'value' => '',
|
||||||
if (class_exists(AssetDefinitionManager::class)) {
|
'after' => 'profiles_id',
|
||||||
try {
|
]);
|
||||||
$manager = AssetDefinitionManager::getInstance();
|
|
||||||
foreach ($manager->getDefinitions() as $definition) {
|
|
||||||
if (!$definition->hasCapacity(UrBackupCapacity::class)) {
|
|
||||||
$definition->enableCapacity(UrBackupCapacity::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$migration->displayMessage(
|
|
||||||
__('Error enabling UrBackup capacity on definitions: ', 'urbackup') . $e->getMessage()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop is_default column (no longer needed in capacity system)
|
$migration->addField($table, 'rights', 'integer', [
|
||||||
if ($has_is_default) {
|
'value' => 0,
|
||||||
$migration->dropField($table, 'is_default');
|
'after' => 'rightname',
|
||||||
}
|
]);
|
||||||
|
|
||||||
|
$migration->addField($table, 'date_creation', 'timestamp');
|
||||||
|
$migration->addField($table, 'date_mod', 'timestamp');
|
||||||
|
|
||||||
|
$migration->addKey($table, ['profiles_id', 'rightname'], 'profile_right');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,24 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_configs` (
|
|||||||
KEY `name` (`name`)
|
KEY `name` (`name`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_assettypes` (
|
||||||
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`itemtype` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
`is_active` TINYINT NOT NULL DEFAULT 0,
|
||||||
|
`is_default` TINYINT NOT NULL DEFAULT 0,
|
||||||
|
`date_creation` TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
`date_mod` TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `itemtype` (`itemtype`),
|
||||||
|
KEY `is_active` (`is_active`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_servers` (
|
CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_servers` (
|
||||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
`entities_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
`entities_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
`is_recursive` TINYINT NOT NULL DEFAULT 0,
|
`is_recursive` TINYINT NOT NULL DEFAULT 0,
|
||||||
`name` VARCHAR(255) NOT NULL DEFAULT '',
|
`name` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
`locations_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
`locations_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
`users_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
|
||||||
`ip_address` VARCHAR(255) NOT NULL DEFAULT '',
|
`ip_address` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
`port` INT UNSIGNED NOT NULL DEFAULT 55414,
|
`port` INT UNSIGNED NOT NULL DEFAULT 55414,
|
||||||
`protocol` VARCHAR(10) NOT NULL DEFAULT 'http',
|
`protocol` VARCHAR(10) NOT NULL DEFAULT 'http',
|
||||||
@@ -33,9 +44,7 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_servers` (
|
|||||||
KEY `name` (`name`),
|
KEY `name` (`name`),
|
||||||
KEY `entities_id` (`entities_id`),
|
KEY `entities_id` (`entities_id`),
|
||||||
KEY `locations_id` (`locations_id`),
|
KEY `locations_id` (`locations_id`),
|
||||||
KEY `users_id` (`users_id`),
|
KEY `is_active` (`is_active`)
|
||||||
KEY `is_active` (`is_active`),
|
|
||||||
KEY `location_active` (`locations_id`, `is_active`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_serverassets` (
|
CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_serverassets` (
|
||||||
@@ -43,11 +52,18 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_serverassets` (
|
|||||||
`plugin_urbackup_servers_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
`plugin_urbackup_servers_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
`itemtype` VARCHAR(255) NOT NULL DEFAULT '',
|
`itemtype` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
`items_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
`items_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
`date_creation` TIMESTAMP NULL DEFAULT NULL,
|
|
||||||
`date_mod` TIMESTAMP NULL DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `plugin_urbackup_servers_id` (`plugin_urbackup_servers_id`),
|
KEY `plugin_urbackup_servers_id` (`plugin_urbackup_servers_id`),
|
||||||
KEY `item` (`itemtype`, `items_id`)
|
KEY `item` (`itemtype`, `items_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
-- glpi_plugin_urbackup_profiles was dropped in 0.7.0 (replaced by Profile::registerRights())
|
CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_profiles` (
|
||||||
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`profiles_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`rightname` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
`rights` INT NOT NULL DEFAULT 0,
|
||||||
|
`date_creation` TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
`date_mod` TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `profile_right` (`profiles_id`, `rightname`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
<?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.', 'urbackup'));
|
die('Sorry. You cannot access this file directly.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* UrBackup API Test JavaScript
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function testApi(serverId, resultBox) {
|
||||||
|
if (!serverId || !resultBox) return;
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/plugins/urbackup/ajax/server_test.php', true);
|
||||||
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
xhr.timeout = 8000;
|
||||||
|
|
||||||
|
xhr.onload = function () {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(xhr.responseText);
|
||||||
|
if (data.success) {
|
||||||
|
resultBox.innerHTML = '<span class="text-success fw-bold"><i class="ti ti-check"></i> ' + 'API connection OK' + '</span>';
|
||||||
|
} else {
|
||||||
|
var message = data.message || 'Connection failed';
|
||||||
|
var isNetwork = /timeout|could not resolve|couldn't connect|connection refused|connection timed out|network is unreachable|no route to host|returned HTTP status/i.test(message);
|
||||||
|
var statusClass = isNetwork ? 'text-warning' : 'text-danger';
|
||||||
|
var icon = isNetwork ? 'ti-wifi-off' : 'ti-x';
|
||||||
|
var label = isNetwork ? 'Server unreachable' : 'API connection failed';
|
||||||
|
resultBox.innerHTML = '<span class="' + statusClass + ' fw-bold"><i class="ti ' + icon + '"></i> ' + label + '</span><br><small class="text-muted">' + message + '</small>';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
resultBox.innerHTML = '<span class="text-danger fw-bold"><i class="ti ti-x"></i> Error</span>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resultBox.innerHTML = '<span class="text-danger fw-bold"><i class="ti ti-x"></i> HTTP ' + xhr.status + '</span>';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
resultBox.innerHTML = '<span class="text-warning fw-bold"><i class="ti ti-wifi-off"></i> Server unreachable</span><br><small class="text-muted">Connection timeout</small>';
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
resultBox.innerHTML = '<span class="text-warning fw-bold"><i class="ti ti-wifi-off"></i> Server unreachable</span><br><small class="text-muted">Network error</small>';
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send('id=' + encodeURIComponent(serverId));
|
||||||
|
}
|
||||||
|
|
||||||
|
function initApiStatusCheck() {
|
||||||
|
var statusBox = document.getElementById('plugin-urbackup-api-status');
|
||||||
|
if (!statusBox) return;
|
||||||
|
if (statusBox._initialized) return;
|
||||||
|
statusBox._initialized = true;
|
||||||
|
|
||||||
|
var serverId = statusBox.getAttribute('data-server-id');
|
||||||
|
if (serverId) {
|
||||||
|
testApi(serverId, statusBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initApiTestButtons() {
|
||||||
|
var buttons = document.querySelectorAll('.plugin-urbackup-test-api');
|
||||||
|
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
if (button._initialized) return;
|
||||||
|
button._initialized = true;
|
||||||
|
|
||||||
|
button.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
var serverId = button.getAttribute('data-server-id');
|
||||||
|
var resultBox = document.getElementById('plugin-urbackup-api-test-result');
|
||||||
|
|
||||||
|
testApi(serverId, resultBox);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initApiStatusCheck);
|
||||||
|
document.addEventListener('DOMContentLoaded', initApiTestButtons);
|
||||||
|
} else {
|
||||||
|
setTimeout(initApiStatusCheck, 100);
|
||||||
|
setTimeout(initApiTestButtons, 100);
|
||||||
|
}
|
||||||
|
})();
|
||||||
Binary file not shown.
@@ -156,57 +156,3 @@ 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."
|
|
||||||
|
|
||||||
msgid "No assets"
|
|
||||||
msgstr "Keine Assets"
|
|
||||||
|
|||||||
Binary file not shown.
@@ -156,57 +156,3 @@ 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."
|
|
||||||
|
|
||||||
msgid "No assets"
|
|
||||||
msgstr "No assets"
|
|
||||||
|
|||||||
Binary file not shown.
@@ -156,57 +156,3 @@ 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."
|
|
||||||
|
|
||||||
msgid "No assets"
|
|
||||||
msgstr "Nessun Asset"
|
|
||||||
|
|||||||
@@ -10,46 +10,3 @@
|
|||||||
color: #b00;
|
color: #b00;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#urbackupTabs .nav-link {
|
|
||||||
font-weight: 600;
|
|
||||||
border-width: 2px;
|
|
||||||
padding: 8px 18px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#urbackupTabs .nav-link#state-tab {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #4a4a4a;
|
|
||||||
border-color: #4a4a4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
#urbackupTabs .nav-link#state-tab.active {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
border-color: #2d2d2d;
|
|
||||||
}
|
|
||||||
|
|
||||||
#urbackupTabs .nav-link#actions-tab {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #6c757d;
|
|
||||||
border-color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
#urbackupTabs .nav-link#actions-tab.active {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #495057;
|
|
||||||
border-color: #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
#urbackupTabs .nav-link#logs-tab {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #adb5bd;
|
|
||||||
border-color: #adb5bd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#urbackupTabs .nav-link#logs-tab.active {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #6c757d;
|
|
||||||
border-color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('POST', '/plugins/urbackup/front/server_test.ajax.php', true);
|
xhr.open('POST', '/plugins/urbackup/front/server_test.ajax.php', true);
|
||||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||||
xhr.setRequestHeader('X-Glpi-Csrf-Token', getAjaxCsrfToken());
|
|
||||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
xhr.timeout = 8000;
|
xhr.timeout = 8000;
|
||||||
|
|
||||||
|
|||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Remove deprecated front/ and ajax/ files for GLPI 11 compliance
|
||||||
|
echo "Removing deprecated front/ and ajax/ files..."
|
||||||
|
rm -f /var/www/glpi/plugins/urbackup/front/config.form.php
|
||||||
|
rm -f /var/www/glpi/plugins/urbackup/front/server.php
|
||||||
|
rm -f /var/www/glpi/plugins/urbackup/front/server.form.php
|
||||||
|
rm -f /var/www/glpi/plugins/urbackup/front/asset.form.php
|
||||||
|
rm -f /var/www/glpi/plugins/urbackup/ajax/server_test.php
|
||||||
|
rm -f /var/www/glpi/plugins/urbackup/ajax/server_clients.php
|
||||||
|
rm -f /var/www/glpi/plugins/urbackup/ajax/asset_action.php
|
||||||
|
echo "Files removed. Note: The plugin now uses Symfony Controllers."
|
||||||
@@ -1,30 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
// Force OPcache to reload this file when accessed via web
|
||||||
|
|
||||||
// Force OPcache to reload plugin files when accessed via web
|
|
||||||
if (PHP_SAPI !== 'cli' && function_exists('opcache_invalidate')) {
|
if (PHP_SAPI !== 'cli' && function_exists('opcache_invalidate')) {
|
||||||
$files = new RecursiveIteratorIterator(
|
opcache_invalidate(__FILE__, true);
|
||||||
new RecursiveDirectoryIterator(__DIR__, RecursiveDirectoryIterator::SKIP_DOTS)
|
|
||||||
);
|
|
||||||
foreach ($files as $file) {
|
|
||||||
if ($file->getExtension() === 'php') {
|
|
||||||
@opcache_invalidate($file->getRealPath(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use Glpi\Asset\AssetDefinitionManager;
|
|
||||||
use Glpi\Plugin\Hooks;
|
use Glpi\Plugin\Hooks;
|
||||||
use GlpiPlugin\Urbackup\AssetTab;
|
use GlpiPlugin\Urbackup\AssetTab;
|
||||||
use GlpiPlugin\Urbackup\Capacity\UrBackupCapacity;
|
|
||||||
use GlpiPlugin\Urbackup\Config;
|
use GlpiPlugin\Urbackup\Config;
|
||||||
use GlpiPlugin\Urbackup\Profile;
|
use GlpiPlugin\Urbackup\Profile;
|
||||||
use GlpiPlugin\Urbackup\Server;
|
use GlpiPlugin\Urbackup\Server;
|
||||||
use GlpiPlugin\Urbackup\ServerAsset;
|
use GlpiPlugin\Urbackup\ServerAsset;
|
||||||
use GlpiPlugin\Urbackup\MassiveAction as PluginUrbackupMassiveAction;
|
use GlpiPlugin\Urbackup\MassiveAction as PluginUrbackupMassiveAction;
|
||||||
|
|
||||||
define('PLUGIN_URBACKUP_VERSION', '0.7.0');
|
define('PLUGIN_URBACKUP_VERSION', '0.4.2');
|
||||||
define('PLUGIN_URBACKUP_MIN_GLPI', '11.0.0');
|
define('PLUGIN_URBACKUP_MIN_GLPI', '11.0.0');
|
||||||
define('PLUGIN_URBACKUP_MAX_GLPI', '11.99.99');
|
define('PLUGIN_URBACKUP_MAX_GLPI', '11.99.99');
|
||||||
|
|
||||||
@@ -61,19 +50,12 @@ function plugin_init_urbackup(): void
|
|||||||
Plugin::registerClass(ServerAsset::class);
|
Plugin::registerClass(ServerAsset::class);
|
||||||
Plugin::registerClass(PluginUrbackupMassiveAction::class);
|
Plugin::registerClass(PluginUrbackupMassiveAction::class);
|
||||||
|
|
||||||
// Register tab on Computer (legacy, always enabled)
|
// Register tab on Computer and other assets
|
||||||
|
$enabled_types = Config::getEnabledItemtypes();
|
||||||
|
if (!empty($enabled_types)) {
|
||||||
Plugin::registerClass(AssetTab::class, [
|
Plugin::registerClass(AssetTab::class, [
|
||||||
'addtabon' => ['Computer'],
|
'addtabon' => $enabled_types,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Register UrBackupCapacity for GLPI 11 Asset Definition types.
|
|
||||||
// Tab registration happens inside `UrBackupCapacity::onClassBootstrap()`
|
|
||||||
// which is called by `bootDefinitions()` during kernel PostBoot event.
|
|
||||||
// This ensures tabs are registered on all definitions that have the
|
|
||||||
// UrBackup capacity enabled, even if they are created after plugin init.
|
|
||||||
if (class_exists(AssetDefinitionManager::class)) {
|
|
||||||
$manager = AssetDefinitionManager::getInstance();
|
|
||||||
$manager->registerCapacity(new UrBackupCapacity());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$PLUGIN_HOOKS['config_page']['urbackup'] = 'front/config.form.php';
|
$PLUGIN_HOOKS['config_page']['urbackup'] = 'front/config.form.php';
|
||||||
@@ -91,6 +73,18 @@ function plugin_init_urbackup(): void
|
|||||||
$PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['urbackup'] = [
|
$PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['urbackup'] = [
|
||||||
'public/js/urbackup.js',
|
'public/js/urbackup.js',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$PLUGIN_HOOKS[Hooks::POST_INIT]['urbackup'] = 'plugin_urbackup_postinit';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post init hook.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function plugin_urbackup_postinit(): void
|
||||||
|
{
|
||||||
|
Config::registerAssetTabs();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,7 +97,7 @@ function plugin_version_urbackup(): array
|
|||||||
return [
|
return [
|
||||||
'name' => __('UrBackup', 'urbackup'),
|
'name' => __('UrBackup', 'urbackup'),
|
||||||
'version' => PLUGIN_URBACKUP_VERSION,
|
'version' => PLUGIN_URBACKUP_VERSION,
|
||||||
'author' => 'Mariano Benzi',
|
'author' => 'Finstral',
|
||||||
'license' => 'GPL-2.0-or-later',
|
'license' => 'GPL-2.0-or-later',
|
||||||
'homepage' => '',
|
'homepage' => '',
|
||||||
'requirements' => [
|
'requirements' => [
|
||||||
|
|||||||
+22
-64
@@ -1,7 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
* UrBackup plugin for GLPI
|
* UrBackup plugin for GLPI
|
||||||
@@ -57,19 +55,15 @@ class AssetTab extends CommonDBTM
|
|||||||
*/
|
*/
|
||||||
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0): string
|
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0): string
|
||||||
{
|
{
|
||||||
$itemtype = $item::class;
|
if (!Config::isItemtypeEnabled($item::class)) {
|
||||||
$enabled = Config::isItemtypeEnabled($itemtype);
|
|
||||||
$hasRight = Profile::canCurrentUser(READ);
|
|
||||||
|
|
||||||
if (!$enabled) {
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$hasRight) {
|
if (!Profile::canCurrentUser(READ)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-up');
|
return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-upload');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,7 +226,7 @@ class AssetTab extends CommonDBTM
|
|||||||
echo "<td>" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . "</td>";
|
echo "<td>" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . "</td>";
|
||||||
echo "<td>" . htmlspecialchars((string) ($server->fields['server_version'] ?? '')) . "</td>";
|
echo "<td>" . htmlspecialchars((string) ($server->fields['server_version'] ?? '')) . "</td>";
|
||||||
echo "<td>" . htmlspecialchars(__('Client name', 'urbackup')) . "</td>";
|
echo "<td>" . htmlspecialchars(__('Client name', 'urbackup')) . "</td>";
|
||||||
echo "<td>" . htmlspecialchars((string) ($item->fields['name'] ?? '')) . "</td>";
|
echo "<td>" . htmlspecialchars(ServerAsset::getAssetName($item::class, (int) $item->fields['id'])) . "</td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
|
|
||||||
echo "</table>";
|
echo "</table>";
|
||||||
@@ -263,14 +257,9 @@ class AssetTab extends CommonDBTM
|
|||||||
*/
|
*/
|
||||||
private static function loadApiData(CommonDBTM $item, Server $server, array $link): array
|
private static function loadApiData(CommonDBTM $item, Server $server, array $link): array
|
||||||
{
|
{
|
||||||
$client_name = (string) ($item->fields['name'] ?? '');
|
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||||||
$asset_ip = ServerAsset::extractAssetIp($item);
|
$asset_ip = ServerAsset::extractAssetIp($item);
|
||||||
|
|
||||||
$cache_key = 'urbackup_data_' . $server->fields['id'] . '_' . $client_name;
|
|
||||||
if (isset($_SESSION[$cache_key]) && $_SESSION[$cache_key]['time'] > time() - 30) {
|
|
||||||
return $_SESSION[$cache_key]['data'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'client_found' => false,
|
'client_found' => false,
|
||||||
'client_status' => [],
|
'client_status' => [],
|
||||||
@@ -314,11 +303,6 @@ class AssetTab extends CommonDBTM
|
|||||||
$data['error'] = $e->getMessage();
|
$data['error'] = $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION[$cache_key] = [
|
|
||||||
'time' => time(),
|
|
||||||
'data' => $data,
|
|
||||||
];
|
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,42 +324,15 @@ class AssetTab extends CommonDBTM
|
|||||||
): void {
|
): void {
|
||||||
echo "<div class='plugin-urbackup-inner-tabs'>";
|
echo "<div class='plugin-urbackup-inner-tabs'>";
|
||||||
|
|
||||||
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
|
echo "<h3>" . htmlspecialchars(__('State', 'urbackup')) . "</h3>";
|
||||||
|
|
||||||
echo '<ul class="nav nav-tabs" id="urbackupTabs" role="tablist">';
|
|
||||||
echo '<li class="nav-item" role="presentation">';
|
|
||||||
echo '<a class="nav-link active" id="state-tab" data-bs-toggle="tab" href="#state" role="tab" aria-selected="true">';
|
|
||||||
echo htmlspecialchars(__('State', 'urbackup'));
|
|
||||||
echo '</a></li>';
|
|
||||||
if ($canWrite) {
|
|
||||||
echo '<li class="nav-item" role="presentation">';
|
|
||||||
echo '<a class="nav-link" id="actions-tab" data-bs-toggle="tab" href="#actions" role="tab" aria-selected="false" tabindex="-1">';
|
|
||||||
echo htmlspecialchars(__('Actions', 'urbackup'));
|
|
||||||
echo '</a></li>';
|
|
||||||
}
|
|
||||||
echo '<li class="nav-item" role="presentation">';
|
|
||||||
echo '<a class="nav-link" id="logs-tab" data-bs-toggle="tab" href="#logs" role="tab" aria-selected="false" tabindex="-1">';
|
|
||||||
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>';
|
|
||||||
|
|
||||||
if ($canWrite) {
|
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 '<div class="tab-pane fade" id="logs" role="tabpanel">';
|
echo "<h3>" . htmlspecialchars(__('Info / Log', 'urbackup')) . "</h3>";
|
||||||
self::showInfoLogSection($api_data);
|
self::showInfoLogSection($api_data);
|
||||||
echo '</div>';
|
|
||||||
|
|
||||||
echo '</div>';
|
|
||||||
echo "</div>";
|
echo "</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,6 +427,17 @@ class AssetTab extends CommonDBTM
|
|||||||
echo "<table class='tab_cadre_fixe'>";
|
echo "<table class='tab_cadre_fixe'>";
|
||||||
echo "<tr><th colspan='2'>" . htmlspecialchars(__('Available actions', 'urbackup')) . "</th></tr>";
|
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)) {
|
if (!$api_data['client_found'] && Profile::canCurrentUser(CREATE)) {
|
||||||
echo "<tr class='tab_bg_1'>";
|
echo "<tr class='tab_bg_1'>";
|
||||||
echo "<td>" . htmlspecialchars(__('Create client in UrBackup', 'urbackup')) . "</td>";
|
echo "<td>" . htmlspecialchars(__('Create client in UrBackup', 'urbackup')) . "</td>";
|
||||||
@@ -497,20 +465,10 @@ 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>";
|
||||||
|
|
||||||
@@ -845,7 +803,7 @@ class AssetTab extends CommonDBTM
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$client_name = (string) ($item->fields['name'] ?? '');
|
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||||||
if ($client_name === '') {
|
if ($client_name === '') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -883,7 +841,7 @@ class AssetTab extends CommonDBTM
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$client_name = (string) ($item->fields['name'] ?? '');
|
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||||||
if ($client_name === '') {
|
if ($client_name === '') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -913,7 +871,7 @@ class AssetTab extends CommonDBTM
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$client_name = (string) ($item->fields['name'] ?? '');
|
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||||||
if ($client_name === '') {
|
if ($client_name === '') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GlpiPlugin\Urbackup\Capacity;
|
|
||||||
|
|
||||||
use CommonGLPI;
|
|
||||||
use Glpi\Asset\Capacity\AbstractCapacity;
|
|
||||||
use Glpi\Asset\CapacityConfig;
|
|
||||||
use GlpiPlugin\Urbackup\AssetTab;
|
|
||||||
use GlpiPlugin\Urbackup\Server;
|
|
||||||
use GlpiPlugin\Urbackup\ServerAsset;
|
|
||||||
|
|
||||||
final class UrBackupCapacity extends AbstractCapacity
|
|
||||||
{
|
|
||||||
public function getLabel(): string
|
|
||||||
{
|
|
||||||
return __('UrBackup', 'urbackup');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIcon(): string
|
|
||||||
{
|
|
||||||
return 'ti ti-cloud-up';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return __('Manage UrBackup backups for these assets', 'urbackup');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onClassBootstrap(string $classname, CapacityConfig $config): void
|
|
||||||
{
|
|
||||||
CommonGLPI::registerStandardTab($classname, AssetTab::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onCapacityDisabled(string $classname, CapacityConfig $config): void
|
|
||||||
{
|
|
||||||
(new ServerAsset())->deleteByCriteria(
|
|
||||||
['itemtype' => $classname],
|
|
||||||
force: true,
|
|
||||||
history: false
|
|
||||||
);
|
|
||||||
$this->deleteRelationLogs($classname, Server::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isUsed(string $classname): bool
|
|
||||||
{
|
|
||||||
return parent::isUsed($classname)
|
|
||||||
&& $this->countAssetsLinkedToPeerItem(
|
|
||||||
$classname,
|
|
||||||
ServerAsset::class
|
|
||||||
) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCapacityUsageDescription(string $classname): string
|
|
||||||
{
|
|
||||||
return sprintf(
|
|
||||||
__('%1$s assets linked to UrBackup servers', 'urbackup'),
|
|
||||||
$this->countAssetsLinkedToPeerItem($classname, ServerAsset::class)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace GlpiPlugin\Urbackup\Command;
|
||||||
|
|
||||||
|
use GlpiPlugin\Urbackup\Server;
|
||||||
|
use GlpiPlugin\Urbackup\UrbackupApiClient;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class TestApiCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('urbackup:test-api')
|
||||||
|
->setDescription('Test UrBackup server API connection')
|
||||||
|
->addArgument('server_id', InputArgument::REQUIRED, 'Server ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$server_id = (int) $input->getArgument('server_id');
|
||||||
|
|
||||||
|
$server = new Server();
|
||||||
|
if (!$server->getFromDB($server_id)) {
|
||||||
|
$output->writeln("<error>Server not found: $server_id</error>");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("Testing API for: " . $server->fields['name']);
|
||||||
|
$output->writeln("URL: " . $server->getWebInterfaceUrl());
|
||||||
|
$output->writeln("Username: " . $server->fields['api_username']);
|
||||||
|
|
||||||
|
$client = new UrbackupApiClient($server);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $client->testConnection();
|
||||||
|
|
||||||
|
if ($result['success']) {
|
||||||
|
$output->writeln("<info>SUCCESS: " . $result['message'] . "</info>");
|
||||||
|
$output->writeln("Identity: " . $result['identity']);
|
||||||
|
} else {
|
||||||
|
$output->writeln("<error>FAILED: " . $result['message'] . "</error>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result['success'] ? Command::SUCCESS : Command::FAILURE;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$output->writeln("<error>Exception: " . $e->getMessage() . "</error>");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+281
-32
@@ -11,7 +11,8 @@ declare(strict_types=1);
|
|||||||
namespace GlpiPlugin\Urbackup;
|
namespace GlpiPlugin\Urbackup;
|
||||||
|
|
||||||
use CommonDBTM;
|
use CommonDBTM;
|
||||||
use Glpi\Asset\Asset;
|
use Computer;
|
||||||
|
use Glpi\Asset\AssetDefinition;
|
||||||
use Glpi\Asset\AssetDefinitionManager;
|
use Glpi\Asset\AssetDefinitionManager;
|
||||||
use DBmysql;
|
use DBmysql;
|
||||||
use Html;
|
use Html;
|
||||||
@@ -52,8 +53,67 @@ class Config extends CommonDBTM
|
|||||||
*/
|
*/
|
||||||
public static function ensureDefaultConfiguration(): void
|
public static function ensureDefaultConfiguration(): void
|
||||||
{
|
{
|
||||||
// Computer is always enabled by default.
|
self::ensureAssetType('Computer', true, true);
|
||||||
// Asset Definition types are managed via the native Capacities UI.
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure an asset type is registered.
|
||||||
|
*
|
||||||
|
* @param string $itemtype Itemtype
|
||||||
|
* @param bool $is_active Active
|
||||||
|
* @param bool $is_default Default
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function ensureAssetType(
|
||||||
|
string $itemtype,
|
||||||
|
bool $is_active = true,
|
||||||
|
bool $is_default = false
|
||||||
|
): void {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
$table = self::getAssetTypesTable();
|
||||||
|
|
||||||
|
if (!$DB->tableExists($table)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing = $DB->request([
|
||||||
|
'FROM' => $table,
|
||||||
|
'WHERE' => [
|
||||||
|
'itemtype' => $itemtype,
|
||||||
|
],
|
||||||
|
'LIMIT' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (count($existing) > 0) {
|
||||||
|
$row = $existing->current();
|
||||||
|
|
||||||
|
$DB->update(
|
||||||
|
$table,
|
||||||
|
[
|
||||||
|
'is_active' => $is_active ? 1 : 0,
|
||||||
|
'is_default' => $is_default ? 1 : 0,
|
||||||
|
'date_mod' => $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => (int) $row['id'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$DB->insert(
|
||||||
|
$table,
|
||||||
|
[
|
||||||
|
'itemtype' => $itemtype,
|
||||||
|
'is_active' => $is_active ? 1 : 0,
|
||||||
|
'is_default' => $is_default ? 1 : 0,
|
||||||
|
'date_creation' => $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s'),
|
||||||
|
'date_mod' => $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s'),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,6 +135,8 @@ class Config extends CommonDBTM
|
|||||||
*/
|
*/
|
||||||
public static function isItemtypeEnabled(string $itemtype): bool
|
public static function isItemtypeEnabled(string $itemtype): bool
|
||||||
{
|
{
|
||||||
|
global $DB;
|
||||||
|
|
||||||
if ($itemtype === '') {
|
if ($itemtype === '') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -83,15 +145,22 @@ class Config extends CommonDBTM
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All Asset subclasses are enabled by default.
|
$table = self::getAssetTypesTable();
|
||||||
// The UrBackupCapacity is still registered for cleanup (onCapacityDisabled)
|
|
||||||
// and usage tracking in the Capacities UI, but visibility is always on
|
if (!$DB->tableExists($table)) {
|
||||||
// so the tab appears out of the box on any Asset Definition.
|
return false;
|
||||||
if (is_a($itemtype, Asset::class, true)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
$iterator = $DB->request([
|
||||||
|
'FROM' => $table,
|
||||||
|
'WHERE' => [
|
||||||
|
'itemtype' => $itemtype,
|
||||||
|
'is_active' => 1,
|
||||||
|
],
|
||||||
|
'LIMIT' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return count($iterator) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,19 +170,54 @@ class Config extends CommonDBTM
|
|||||||
*/
|
*/
|
||||||
public static function getEnabledItemtypes(): array
|
public static function getEnabledItemtypes(): array
|
||||||
{
|
{
|
||||||
$itemtypes = ['Computer'];
|
global $DB;
|
||||||
|
|
||||||
if (class_exists(AssetDefinitionManager::class)) {
|
$types = ['Computer'];
|
||||||
$manager = AssetDefinitionManager::getInstance();
|
$table = self::getAssetTypesTable();
|
||||||
foreach ($manager->getDefinitions() as $definition) {
|
|
||||||
$classname = $definition->getAssetClassName();
|
if (!$DB->tableExists($table)) {
|
||||||
if (!in_array($classname, $itemtypes, true)) {
|
return $types;
|
||||||
$itemtypes[] = $classname;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$iterator = $DB->request([
|
||||||
|
'FROM' => $table,
|
||||||
|
'WHERE' => [
|
||||||
|
'is_active' => 1,
|
||||||
|
],
|
||||||
|
'ORDER' => 'itemtype',
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($iterator as $row) {
|
||||||
|
$itemtype = (string) $row['itemtype'];
|
||||||
|
|
||||||
|
if ($itemtype !== '' && !in_array($itemtype, $types, true)) {
|
||||||
|
$types[] = $itemtype;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $itemtypes;
|
return $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register tabs on enabled asset types.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function registerAssetTabs(): void
|
||||||
|
{
|
||||||
|
foreach (self::getEnabledItemtypes() as $itemtype) {
|
||||||
|
if (!class_exists($itemtype)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_a($itemtype, CommonDBTM::class, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
\Plugin::registerClass(AssetTab::class, [
|
||||||
|
'addtabon' => $itemtype,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,32 +232,78 @@ class Config extends CommonDBTM
|
|||||||
{
|
{
|
||||||
Session::checkRight('config', UPDATE);
|
Session::checkRight('config', UPDATE);
|
||||||
|
|
||||||
|
echo "<form method='post' action='" . htmlspecialchars($options['target'] ?? '') . "'>";
|
||||||
echo "<div class='center'>";
|
echo "<div class='center'>";
|
||||||
echo "<table class='tab_cadre_fixe'>";
|
echo "<table class='tab_cadre_fixe'>";
|
||||||
echo "<tr><th colspan='2'>" . htmlspecialchars(__('UrBackup configuration', 'urbackup')) . "</th></tr>";
|
echo "<tr><th colspan='4'>" . htmlspecialchars(__('UrBackup configuration', 'urbackup')) . "</th></tr>";
|
||||||
echo "<tr class='tab_bg_1'>";
|
echo "<tr>";
|
||||||
echo "<td><strong>" . htmlspecialchars(__('Computer', 'urbackup')) . "</strong></td>";
|
echo "<th>" . htmlspecialchars(__('Asset type', 'urbackup')) . "</th>";
|
||||||
echo "<td><span class='badge bg-success'>" . htmlspecialchars(__('Always enabled', 'urbackup')) . "</span></td>";
|
echo "<th>" . htmlspecialchars(__('Enabled', 'urbackup')) . "</th>";
|
||||||
|
echo "<th>" . htmlspecialchars(__('Default', 'urbackup')) . "</th>";
|
||||||
|
echo "<th>" . htmlspecialchars(__('Type', 'urbackup')) . "</th>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
echo "</table>";
|
|
||||||
|
|
||||||
echo "<br>";
|
foreach (self::getConfigurableAssetTypes() as $itemtype => $label) {
|
||||||
echo "<div class='alert alert-info'>";
|
$enabled = self::isItemtypeEnabled($itemtype);
|
||||||
echo htmlspecialchars(
|
$is_default = ($itemtype === 'Computer');
|
||||||
__('For Asset Definition types, enable/disable UrBackup via Config > Asset definitions > Capacities.', 'urbackup')
|
$is_asset_definition = self::isAssetDefinition($itemtype);
|
||||||
);
|
|
||||||
echo "</div>";
|
echo "<tr class='tab_bg_1'>";
|
||||||
|
echo "<td>" . htmlspecialchars($label) . "</td>";
|
||||||
|
|
||||||
|
if ($itemtype === 'Computer') {
|
||||||
|
echo "<td>" . htmlspecialchars(__('Always', 'urbackup')) . "</td>";
|
||||||
|
echo "<td>" . htmlspecialchars(__('Yes', 'urbackup')) . "</td>";
|
||||||
|
} elseif ($is_asset_definition) {
|
||||||
|
// GLPI 11 Asset Definition
|
||||||
|
echo "<td>";
|
||||||
|
Html::showCheckbox([
|
||||||
|
'name' => 'assettypes[' . htmlspecialchars($itemtype) . '][is_active]',
|
||||||
|
'checked' => $enabled,
|
||||||
|
]);
|
||||||
|
echo "</td>";
|
||||||
|
echo "<td>";
|
||||||
|
Html::showCheckbox([
|
||||||
|
'name' => 'assettypes[' . htmlspecialchars($itemtype) . '][is_default]',
|
||||||
|
'checked' => $is_default,
|
||||||
|
]);
|
||||||
|
echo "</td>";
|
||||||
|
echo "<td><span class='badge bg-info'>Asset Definition</span></td>";
|
||||||
|
} else {
|
||||||
|
// Legacy type
|
||||||
|
echo "<td>";
|
||||||
|
Html::showCheckbox([
|
||||||
|
'name' => 'assettypes[' . htmlspecialchars($itemtype) . ']',
|
||||||
|
'checked' => $enabled,
|
||||||
|
]);
|
||||||
|
echo "</td>";
|
||||||
|
echo "<td>" . ($is_default ? htmlspecialchars(__('Yes', 'urbackup')) : '') . "</td>";
|
||||||
|
echo "<td><span class='badge bg-secondary'>Legacy</span></td>";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "<tr>";
|
||||||
|
echo "<td colspan='4' class='center'>";
|
||||||
|
echo Html::submit(__('Save', 'urbackup'), [
|
||||||
|
'name' => 'update',
|
||||||
|
'class' => 'btn btn-primary',
|
||||||
|
]);
|
||||||
|
echo "</td>";
|
||||||
|
echo "</tr>";
|
||||||
|
|
||||||
|
echo "</table>";
|
||||||
echo "</div>";
|
echo "</div>";
|
||||||
|
|
||||||
|
Html::closeForm();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save configuration.
|
* Save configuration.
|
||||||
*
|
*
|
||||||
* In the new capacity system, asset types are managed via the native Capacities UI.
|
|
||||||
* This method is kept for backward compatibility but does nothing.
|
|
||||||
*
|
|
||||||
* @param array<string, mixed> $input Input data
|
* @param array<string, mixed> $input Input data
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
@@ -161,5 +311,104 @@ class Config extends CommonDBTM
|
|||||||
public static function saveConfiguration(array $input): void
|
public static function saveConfiguration(array $input): void
|
||||||
{
|
{
|
||||||
Session::checkRight('config', UPDATE);
|
Session::checkRight('config', UPDATE);
|
||||||
|
|
||||||
|
$selected = $input['assettypes'] ?? [];
|
||||||
|
|
||||||
|
foreach (self::getConfigurableAssetTypes() as $itemtype => $label) {
|
||||||
|
if ($itemtype === 'Computer') {
|
||||||
|
self::ensureAssetType('Computer', true, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an Asset Definition (new format with is_active/is_default)
|
||||||
|
if (self::isAssetDefinition($itemtype)) {
|
||||||
|
$is_active = isset($selected[$itemtype]['is_active']) && (bool) $selected[$itemtype]['is_active'];
|
||||||
|
$is_default = isset($selected[$itemtype]['is_default']) && (bool) $selected[$itemtype]['is_default'];
|
||||||
|
self::ensureAssetType($itemtype, $is_active, $is_default);
|
||||||
|
} else {
|
||||||
|
// Legacy format (simple checkbox)
|
||||||
|
$is_enabled = isset($selected[$itemtype]) && (bool) $selected[$itemtype];
|
||||||
|
self::ensureAssetType($itemtype, $is_enabled, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configurable asset types (legacy + GLPI 11 Asset Definition).
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public static function getConfigurableAssetTypes(): array
|
||||||
|
{
|
||||||
|
$types = [
|
||||||
|
'Computer' => Computer::getTypeName(Session::getPluralNumber()),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Legacy types
|
||||||
|
$known_types = [
|
||||||
|
'Printer',
|
||||||
|
'Peripheral',
|
||||||
|
'NetworkEquipment',
|
||||||
|
'Phone',
|
||||||
|
'Monitor',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($known_types as $itemtype) {
|
||||||
|
if (!class_exists($itemtype)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_a($itemtype, CommonDBTM::class, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$types[$itemtype] = $itemtype::getTypeName(Session::getPluralNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
// GLPI 11 Asset Definition
|
||||||
|
if (class_exists(AssetDefinitionManager::class)) {
|
||||||
|
try {
|
||||||
|
$assetDefinitions = AssetDefinitionManager::getInstance()->getDefinitions(true);
|
||||||
|
foreach ($assetDefinitions as $definition) {
|
||||||
|
// Access fields directly like CommonDBTM
|
||||||
|
$system_name = $definition->fields['system_name'] ?? '';
|
||||||
|
$name = $definition->fields['name'] ?? '';
|
||||||
|
if (!empty($system_name)) {
|
||||||
|
$types[$system_name] = !empty($name) ? $name : $system_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// If AssetDefinitionManager fails, skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if itemtype is a GLPI 11 Asset Definition.
|
||||||
|
*
|
||||||
|
* @param string $itemtype Itemtype or system name
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isAssetDefinition(string $itemtype): bool
|
||||||
|
{
|
||||||
|
if (!class_exists(AssetDefinitionManager::class)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$assetDefinitions = AssetDefinitionManager::getInstance()->getDefinitions(true);
|
||||||
|
foreach ($assetDefinitions as $definition) {
|
||||||
|
if (($definition->fields['system_name'] ?? '') === $itemtype) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// If fails, return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
* UrBackup plugin for GLPI
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace GlpiPlugin\Urbackup\Controller;
|
||||||
|
|
||||||
|
use GlpiPlugin\Urbackup\Config;
|
||||||
|
use GlpiPlugin\Urbackup\Profile;
|
||||||
|
use GlpiPlugin\Urbackup\Server;
|
||||||
|
use GlpiPlugin\Urbackup\ServerAsset;
|
||||||
|
use GlpiPlugin\Urbackup\UrbackupApiClient;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use CommonDBTM;
|
||||||
|
use Html;
|
||||||
|
use Session;
|
||||||
|
|
||||||
|
class AssetController
|
||||||
|
{
|
||||||
|
#[Route('/plugin/urbackup/asset/action', name: 'urbackup_asset_action', methods: ['POST'])]
|
||||||
|
public function assetAction(Request $request): void
|
||||||
|
{
|
||||||
|
Session::checkLoginUser();
|
||||||
|
Session::checkCSRF($_POST);
|
||||||
|
|
||||||
|
$itemtype = (string) $request->request->get('itemtype', '');
|
||||||
|
$items_id = (int) $request->request->get('items_id', 0);
|
||||||
|
|
||||||
|
if ($itemtype === '' || $items_id <= 0 || !class_exists($itemtype)) {
|
||||||
|
Session::addMessageAfterRedirect(
|
||||||
|
__('Invalid asset reference.', 'urbackup'),
|
||||||
|
true,
|
||||||
|
ERROR
|
||||||
|
);
|
||||||
|
Html::back();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config::isItemtypeEnabled($itemtype)) {
|
||||||
|
Session::addMessageAfterRedirect(
|
||||||
|
__('UrBackup is not enabled for this asset type.', 'urbackup'),
|
||||||
|
true,
|
||||||
|
ERROR
|
||||||
|
);
|
||||||
|
Html::back();
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = new $itemtype();
|
||||||
|
|
||||||
|
if (!$item instanceof CommonDBTM || !$item->getFromDB($items_id)) {
|
||||||
|
Session::addMessageAfterRedirect(
|
||||||
|
__('Asset not found.', 'urbackup'),
|
||||||
|
true,
|
||||||
|
ERROR
|
||||||
|
);
|
||||||
|
Html::back();
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = (string) $request->request->get('urbackup_action', '');
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'connect':
|
||||||
|
$server_id = (int) $request->request->get('plugin_urbackup_servers_id', 0);
|
||||||
|
if (ServerAsset::connectAssetToServer($itemtype, $items_id, $server_id)) {
|
||||||
|
Session::addMessageAfterRedirect(
|
||||||
|
__('Asset connected to UrBackup server.', 'urbackup'),
|
||||||
|
true,
|
||||||
|
INFO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'disconnect':
|
||||||
|
if (ServerAsset::disconnectAsset($itemtype, $items_id)) {
|
||||||
|
Session::addMessageAfterRedirect(
|
||||||
|
__('Asset disconnected from UrBackup server.', 'urbackup'),
|
||||||
|
true,
|
||||||
|
INFO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'set_internet_mode':
|
||||||
|
if (Profile::canCurrentUser(UPDATE)) {
|
||||||
|
$enabled = (int) $request->request->get('internet_mode', 0) === 1;
|
||||||
|
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, true);
|
||||||
|
if ($link) {
|
||||||
|
$server = new Server();
|
||||||
|
if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) {
|
||||||
|
$api = new UrbackupApiClient($server);
|
||||||
|
$client_name = (string) ($item->fields['name'] ?? '');
|
||||||
|
$api->saveInternetMode($client_name, $enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'create_client':
|
||||||
|
if (Profile::canCurrentUser(CREATE)) {
|
||||||
|
$serverAsset = new ServerAsset();
|
||||||
|
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, true);
|
||||||
|
if ($link) {
|
||||||
|
$server = new Server();
|
||||||
|
if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) {
|
||||||
|
$api = new UrbackupApiClient($server);
|
||||||
|
$client_name = (string) ($item->fields['name'] ?? '');
|
||||||
|
$api->addClient($client_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'incremental_file_backup':
|
||||||
|
case 'full_file_backup':
|
||||||
|
case 'incremental_image_backup':
|
||||||
|
case 'full_image_backup':
|
||||||
|
if (Profile::canCurrentUser(UPDATE)) {
|
||||||
|
$serverAsset = new ServerAsset();
|
||||||
|
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, true);
|
||||||
|
if ($link) {
|
||||||
|
$server = new Server();
|
||||||
|
if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) {
|
||||||
|
$api = new UrbackupApiClient($server);
|
||||||
|
$client_name = (string) ($item->fields['name'] ?? '');
|
||||||
|
switch ($action) {
|
||||||
|
case 'incremental_file_backup':
|
||||||
|
$api->startIncrementalFileBackup($client_name);
|
||||||
|
break;
|
||||||
|
case 'full_file_backup':
|
||||||
|
$api->startFullFileBackup($client_name);
|
||||||
|
break;
|
||||||
|
case 'incremental_image_backup':
|
||||||
|
$api->startIncrementalImageBackup($client_name);
|
||||||
|
break;
|
||||||
|
case 'full_image_backup':
|
||||||
|
$api->startFullImageBackup($client_name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'delete_client':
|
||||||
|
if (Profile::canCurrentUser(PURGE)) {
|
||||||
|
$serverAsset = new ServerAsset();
|
||||||
|
$link = ServerAsset::getLinkForAsset($itemtype, $items_id, true);
|
||||||
|
if ($link) {
|
||||||
|
$server = new Server();
|
||||||
|
if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) {
|
||||||
|
$api = new UrbackupApiClient($server);
|
||||||
|
$client_name = (string) ($item->fields['name'] ?? '');
|
||||||
|
$api->removeClient($client_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Html::back();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/plugin/urbackup/api/clients', name: 'urbackup_api_clients', methods: ['GET'])]
|
||||||
|
public function getClients(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
Session::checkLoginUser();
|
||||||
|
|
||||||
|
$server_id = (int) $request->query->get('server_id', 0);
|
||||||
|
|
||||||
|
if ($server_id <= 0) {
|
||||||
|
return new JsonResponse([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = new Server();
|
||||||
|
|
||||||
|
if (!$server->getFromDB($server_id)) {
|
||||||
|
return new JsonResponse([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = new UrbackupApiClient($server);
|
||||||
|
$clients = $api->getStatus();
|
||||||
|
|
||||||
|
return new JsonResponse($clients);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
* UrBackup plugin for GLPI
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace GlpiPlugin\Urbackup\Controller;
|
||||||
|
|
||||||
|
use GlpiPlugin\Urbackup\Config;
|
||||||
|
use GlpiPlugin\Urbackup\Profile;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Html;
|
||||||
|
use Session;
|
||||||
|
|
||||||
|
class ConfigController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/plugin/urbackup/config', name: 'urbackup_config', methods: ['GET', 'POST'])]
|
||||||
|
public function configure(Request $request): Response
|
||||||
|
{
|
||||||
|
Session::checkRight('config', UPDATE);
|
||||||
|
|
||||||
|
if ($request->isMethod('POST') && $request->request->has('update')) {
|
||||||
|
Config::saveConfiguration($request->request->all());
|
||||||
|
Html::back();
|
||||||
|
}
|
||||||
|
|
||||||
|
$configurableTypes = Config::getConfigurableAssetTypes();
|
||||||
|
$enabledTypes = [];
|
||||||
|
foreach ($configurableTypes as $itemtype => $label) {
|
||||||
|
$enabledTypes[$itemtype] = Config::isItemtypeEnabled($itemtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('config/config.html.twig', [
|
||||||
|
'configurable_types' => $configurableTypes,
|
||||||
|
'enabled_types' => $enabledTypes,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
* UrBackup plugin for GLPI
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace GlpiPlugin\Urbackup\Controller;
|
||||||
|
|
||||||
|
use GlpiPlugin\Urbackup\Server;
|
||||||
|
use GlpiPlugin\Urbackup\Profile;
|
||||||
|
use GlpiPlugin\Urbackup\UrbackupApiClient;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Html;
|
||||||
|
use Session;
|
||||||
|
use Search;
|
||||||
|
|
||||||
|
class ServerController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/plugin/urbackup/servers', name: 'urbackup_server_list', methods: ['GET'])]
|
||||||
|
public function listServers(): void
|
||||||
|
{
|
||||||
|
if (!Server::canView()) {
|
||||||
|
Html::displayRightError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Html::header(
|
||||||
|
Server::getTypeName(Session::getPluralNumber()),
|
||||||
|
$_SERVER['PHP_SELF'],
|
||||||
|
'admin',
|
||||||
|
Server::class
|
||||||
|
);
|
||||||
|
|
||||||
|
Search::show(Server::class);
|
||||||
|
|
||||||
|
Html::footer();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/plugin/urbackup/server/{id}', name: 'urbackup_server_show', methods: ['GET'], requirements: ['id' => '\d+'])]
|
||||||
|
public function showServer(int $id): void
|
||||||
|
{
|
||||||
|
$server = new Server();
|
||||||
|
|
||||||
|
if ($id > 0) {
|
||||||
|
$server->check($id, READ);
|
||||||
|
} else {
|
||||||
|
$server->check(-1, CREATE);
|
||||||
|
$server->getEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
$server->display(['id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/plugin/urbackup/server/test/{id}', name: 'urbackup_server_test', methods: ['POST', 'GET'])]
|
||||||
|
public function testConnection(int $id = 0): JsonResponse
|
||||||
|
{
|
||||||
|
if (!Profile::canCurrentUser(READ)) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No permission',
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
$id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid server ID',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = new Server();
|
||||||
|
|
||||||
|
if (!$server->getFromDB($id)) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Server not found',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = new UrbackupApiClient($server);
|
||||||
|
$result = $client->testConnection();
|
||||||
|
|
||||||
|
$server->update([
|
||||||
|
'id' => $id,
|
||||||
|
'last_api_status' => $result['success'] ? 1 : 0,
|
||||||
|
'last_api_message' => $result['message'],
|
||||||
|
'last_api_check' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new JsonResponse($result);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
* UrBackup plugin for GLPI
|
* UrBackup plugin for GLPI
|
||||||
|
|||||||
+3
-18
@@ -14,7 +14,7 @@ class Profile extends \Profile
|
|||||||
|
|
||||||
public static function getIcon()
|
public static function getIcon()
|
||||||
{
|
{
|
||||||
return 'ti ti-cloud-up';
|
return 'ti ti-server';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
|
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
|
||||||
@@ -73,22 +73,7 @@ class Profile extends \Profile
|
|||||||
$profiles_id = (int) ($_SESSION['glpiactiveprofile']['id'] ?? 0);
|
$profiles_id = (int) ($_SESSION['glpiactiveprofile']['id'] ?? 0);
|
||||||
|
|
||||||
if ($profiles_id > 0) {
|
if ($profiles_id > 0) {
|
||||||
self::setProfileRights($profiles_id, READ | UPDATE | CREATE | DELETE | PURGE);
|
self::setProfileRights($profiles_id, READ | UPDATE | CREATE | DELETE);
|
||||||
} else {
|
|
||||||
// CLI installation: no session available.
|
|
||||||
// Assign full rights to the Super-Admin profile.
|
|
||||||
global $DB;
|
|
||||||
$sa_iterator = $DB->request([
|
|
||||||
'FROM' => 'glpi_profiles',
|
|
||||||
'WHERE' => [
|
|
||||||
'name' => 'Super-Admin',
|
|
||||||
],
|
|
||||||
'LIMIT' => 1,
|
|
||||||
]);
|
|
||||||
foreach ($sa_iterator as $sa_profile) {
|
|
||||||
$profiles_id = (int) $sa_profile['id'];
|
|
||||||
self::setProfileRights($profiles_id, READ | UPDATE | CREATE | DELETE | PURGE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global $DB;
|
global $DB;
|
||||||
@@ -98,7 +83,7 @@ class Profile extends \Profile
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($all_profiles as $profile) {
|
foreach ($all_profiles as $profile) {
|
||||||
if ((int) $profile['id'] !== $profiles_id) {
|
if ($profile['id'] !== $profiles_id) {
|
||||||
$existing = $DB->request([
|
$existing = $DB->request([
|
||||||
'FROM' => 'glpi_profilerights',
|
'FROM' => 'glpi_profilerights',
|
||||||
'WHERE' => [
|
'WHERE' => [
|
||||||
|
|||||||
+18
-548
@@ -1,7 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
* UrBackup plugin for GLPI
|
* UrBackup plugin for GLPI
|
||||||
@@ -14,12 +12,9 @@ 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
|
||||||
{
|
{
|
||||||
@@ -156,7 +151,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-cloud-up';
|
$menu['icon'] = 'ti ti-server';
|
||||||
|
|
||||||
$menu['links']['search'] = self::getSearchURL(false);
|
$menu['links']['search'] = self::getSearchURL(false);
|
||||||
|
|
||||||
@@ -259,14 +254,6 @@ class Server extends CommonDBTM
|
|||||||
|
|
||||||
$tab[] = [
|
$tab[] = [
|
||||||
'id' => 8,
|
'id' => 8,
|
||||||
'table' => User::getTable(),
|
|
||||||
'field' => 'name',
|
|
||||||
'name' => User::getTypeName(1),
|
|
||||||
'datatype' => 'dropdown',
|
|
||||||
];
|
|
||||||
|
|
||||||
$tab[] = [
|
|
||||||
'id' => 9,
|
|
||||||
'table' => self::getTable(),
|
'table' => self::getTable(),
|
||||||
'field' => 'is_active',
|
'field' => 'is_active',
|
||||||
'name' => __('Active'),
|
'name' => __('Active'),
|
||||||
@@ -274,7 +261,7 @@ class Server extends CommonDBTM
|
|||||||
];
|
];
|
||||||
|
|
||||||
$tab[] = [
|
$tab[] = [
|
||||||
'id' => 10,
|
'id' => 9,
|
||||||
'table' => self::getTable(),
|
'table' => self::getTable(),
|
||||||
'field' => 'last_api_status',
|
'field' => 'last_api_status',
|
||||||
'name' => __('Last API status', 'urbackup'),
|
'name' => __('Last API status', 'urbackup'),
|
||||||
@@ -282,7 +269,7 @@ class Server extends CommonDBTM
|
|||||||
];
|
];
|
||||||
|
|
||||||
$tab[] = [
|
$tab[] = [
|
||||||
'id' => 11,
|
'id' => 10,
|
||||||
'table' => self::getTable(),
|
'table' => self::getTable(),
|
||||||
'field' => 'last_api_check',
|
'field' => 'last_api_check',
|
||||||
'name' => __('Last API check', 'urbackup'),
|
'name' => __('Last API check', 'urbackup'),
|
||||||
@@ -290,7 +277,7 @@ class Server extends CommonDBTM
|
|||||||
];
|
];
|
||||||
|
|
||||||
$tab[] = [
|
$tab[] = [
|
||||||
'id' => 12,
|
'id' => 11,
|
||||||
'table' => self::getTable(),
|
'table' => self::getTable(),
|
||||||
'field' => 'date_creation',
|
'field' => 'date_creation',
|
||||||
'name' => __('Creation date'),
|
'name' => __('Creation date'),
|
||||||
@@ -298,16 +285,15 @@ class Server extends CommonDBTM
|
|||||||
];
|
];
|
||||||
|
|
||||||
$tab[] = [
|
$tab[] = [
|
||||||
'id' => 13,
|
'id' => 12,
|
||||||
'table' => self::getTable(),
|
'table' => self::getTable(),
|
||||||
'field' => 'date_mod',
|
'field' => 'date_mod',
|
||||||
'name' => __('Last update'),
|
'name' => __('Last update'),
|
||||||
'datatype' => 'datetime',
|
'datatype' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (Session::haveRight(self::$rightname, UPDATE)) {
|
|
||||||
$tab[] = [
|
$tab[] = [
|
||||||
'id' => 14,
|
'id' => 13,
|
||||||
'table' => self::getTable(),
|
'table' => self::getTable(),
|
||||||
'field' => 'id',
|
'field' => 'id',
|
||||||
'name' => __('View', 'urbackup'),
|
'name' => __('View', 'urbackup'),
|
||||||
@@ -315,7 +301,6 @@ class Server extends CommonDBTM
|
|||||||
'datatype' => 'raw',
|
'datatype' => 'raw',
|
||||||
'searchtype' => 'view',
|
'searchtype' => 'view',
|
||||||
];
|
];
|
||||||
}
|
|
||||||
|
|
||||||
return $tab;
|
return $tab;
|
||||||
}
|
}
|
||||||
@@ -339,14 +324,7 @@ 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>";
|
||||||
@@ -358,10 +336,7 @@ class Server extends CommonDBTM
|
|||||||
|
|
||||||
echo "<td>" . htmlspecialchars(__('Active')) . "</td>";
|
echo "<td>" . htmlspecialchars(__('Active')) . "</td>";
|
||||||
echo "<td>";
|
echo "<td>";
|
||||||
Dropdown::showYesNo(
|
Dropdown::showYesNo('is_active', (int) ($this->fields['is_active'] ?? 1));
|
||||||
'is_active',
|
|
||||||
($ID !== null && $ID > 0) ? (int) ($this->fields['is_active'] ?? 1) : 1
|
|
||||||
);
|
|
||||||
echo "</td>";
|
echo "</td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
|
|
||||||
@@ -399,8 +374,8 @@ class Server extends CommonDBTM
|
|||||||
Dropdown::showFromArray(
|
Dropdown::showFromArray(
|
||||||
'protocol',
|
'protocol',
|
||||||
[
|
[
|
||||||
'http' => __('HTTP', 'urbackup'),
|
'http' => 'HTTP',
|
||||||
'https' => __('HTTPS', 'urbackup'),
|
'https' => 'HTTPS',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'value' => $this->fields['protocol'] ?? 'http',
|
'value' => $this->fields['protocol'] ?? 'http',
|
||||||
@@ -444,33 +419,21 @@ class Server extends CommonDBTM
|
|||||||
echo "</td>";
|
echo "</td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
|
|
||||||
$canEdit = $ID > 0
|
|
||||||
? Session::haveRight(self::$rightname, UPDATE)
|
|
||||||
: Session::haveRight(self::$rightname, CREATE);
|
|
||||||
|
|
||||||
echo "<tr class='tab_bg_1'>";
|
echo "<tr class='tab_bg_1'>";
|
||||||
echo "<td>" . htmlspecialchars(__('API username', 'urbackup')) . "</td>";
|
echo "<td>" . htmlspecialchars(__('API username', 'urbackup')) . "</td>";
|
||||||
echo "<td>";
|
echo "<td>";
|
||||||
if ($canEdit) {
|
|
||||||
echo Html::input('api_username', [
|
echo Html::input('api_username', [
|
||||||
'value' => $this->fields['api_username'] ?? '',
|
'value' => $this->fields['api_username'] ?? '',
|
||||||
'size' => 40,
|
'size' => 40,
|
||||||
'autocomplete' => 'off',
|
'autocomplete' => 'off',
|
||||||
]);
|
]);
|
||||||
} else {
|
|
||||||
echo htmlspecialchars($this->fields['api_username'] ?? '');
|
|
||||||
}
|
|
||||||
echo "</td>";
|
echo "</td>";
|
||||||
|
|
||||||
echo "<td>" . htmlspecialchars(__('API password', 'urbackup')) . "</td>";
|
echo "<td>" . htmlspecialchars(__('API password', 'urbackup')) . "</td>";
|
||||||
echo "<td>";
|
echo "<td>";
|
||||||
if ($canEdit) {
|
|
||||||
echo "<input type='password' name='api_password' value='" .
|
echo "<input type='password' name='api_password' value='" .
|
||||||
htmlspecialchars((string) ($this->fields['api_password'] ?? '')) .
|
htmlspecialchars((string) ($this->fields['api_password'] ?? '')) .
|
||||||
"' autocomplete='new-password'>";
|
"' autocomplete='new-password'>";
|
||||||
} else {
|
|
||||||
echo '******';
|
|
||||||
}
|
|
||||||
echo "</td>";
|
echo "</td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
|
|
||||||
@@ -516,6 +479,10 @@ class Server extends CommonDBTM
|
|||||||
echo "</td>";
|
echo "</td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->showFormButtons($options);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -575,30 +542,6 @@ class Server extends CommonDBTM
|
|||||||
|
|
||||||
public function prepareInputForAdd(mixed $input): mixed
|
public function prepareInputForAdd(mixed $input): mixed
|
||||||
{
|
{
|
||||||
if (!is_array($input)) {
|
|
||||||
return $input;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($input['users_id']) || $input['users_id'] <= 0) {
|
|
||||||
$input['users_id'] = (int) ($_SESSION['glpiID'] ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,16 +551,6 @@ class Server extends CommonDBTM
|
|||||||
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'])) {
|
||||||
@@ -847,7 +780,7 @@ class Server extends CommonDBTM
|
|||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo '<div class="alert alert-danger">';
|
echo '<div class="alert alert-danger">';
|
||||||
echo htmlspecialchars(__('API Error', 'urbackup')) . ': ' . htmlspecialchars($e->getMessage());
|
echo 'API Error: ' . htmlspecialchars($e->getMessage());
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -856,6 +789,7 @@ class Server extends CommonDBTM
|
|||||||
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>';
|
||||||
@@ -906,14 +840,8 @@ class Server extends CommonDBTM
|
|||||||
$itemUrl = $glpiItem ? $glpiItem->getLinkURL() : '';
|
$itemUrl = $glpiItem ? $glpiItem->getLinkURL() : '';
|
||||||
|
|
||||||
echo '<tr>';
|
echo '<tr>';
|
||||||
echo '<td>';
|
echo '<td>' . htmlspecialchars($itemTypeLabel . ' #' . $link['items_id']) . '</td>';
|
||||||
if ($itemUrl) {
|
echo '<td>' . ($itemUrl ? '<a href="' . htmlspecialchars($itemUrl) . '">' . htmlspecialchars($clientName) . '</a>' : htmlspecialchars($clientName)) . '</td>';
|
||||||
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>';
|
||||||
@@ -1021,8 +949,6 @@ class Server extends CommonDBTM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
|
|
||||||
|
|
||||||
echo '<table class="table table-striped table-hover">';
|
echo '<table class="table table-striped table-hover">';
|
||||||
echo '<thead>';
|
echo '<thead>';
|
||||||
echo '<tr>';
|
echo '<tr>';
|
||||||
@@ -1031,15 +957,13 @@ class Server extends CommonDBTM
|
|||||||
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>';
|
||||||
if ($canWrite) {
|
|
||||||
echo '<th>' . htmlspecialchars(__('Actions', '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'));
|
$clientName = (string) ($uc['name'] ?? 'Unknown');
|
||||||
$clientNameLower = strtolower($clientName);
|
$clientNameLower = strtolower($clientName);
|
||||||
|
|
||||||
$online = $uc['online'] ?? null;
|
$online = $uc['online'] ?? null;
|
||||||
@@ -1058,7 +982,6 @@ class Server extends CommonDBTM
|
|||||||
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>';
|
||||||
if ($canWrite) {
|
|
||||||
echo '<td>';
|
echo '<td>';
|
||||||
if (isset($linkableAssets[$clientNameLower])) {
|
if (isset($linkableAssets[$clientNameLower])) {
|
||||||
$match = $linkableAssets[$clientNameLower];
|
$match = $linkableAssets[$clientNameLower];
|
||||||
@@ -1076,463 +999,10 @@ class Server extends CommonDBTM
|
|||||||
echo '<span class="text-muted">—</span>';
|
echo '<span class="text-muted">—</span>';
|
||||||
}
|
}
|
||||||
echo '</td>';
|
echo '</td>';
|
||||||
}
|
|
||||||
echo '</tr>';
|
echo '</tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '</tbody>';
|
echo '</tbody>';
|
||||||
echo '</table>';
|
echo '</table>';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function showMissingClientsTab(Server $server): void
|
|
||||||
{
|
|
||||||
global $DB;
|
|
||||||
|
|
||||||
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
|
|
||||||
|
|
||||||
$apiStatus = (int) ($server->fields['last_api_status'] ?? 0);
|
|
||||||
if ($apiStatus !== 1) {
|
|
||||||
echo '<div class="alert alert-warning">';
|
|
||||||
echo htmlspecialchars(__('API connection not working. Save server to test connection.', 'urbackup'));
|
|
||||||
echo '</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$serverLocationId = (int) ($server->fields['locations_id'] ?? 0);
|
|
||||||
if ($serverLocationId <= 0) {
|
|
||||||
echo '<div class="alert alert-info">';
|
|
||||||
echo htmlspecialchars(__('No location configured for this server.', 'urbackup'));
|
|
||||||
echo '</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootLocationId = LocationHelper::getRootLocationId($serverLocationId);
|
|
||||||
|
|
||||||
$linkedIterator = $DB->request([
|
|
||||||
'FROM' => ServerAsset::getTable(),
|
|
||||||
]);
|
|
||||||
$linkedAssetKeys = [];
|
|
||||||
foreach ($linkedIterator as $row) {
|
|
||||||
$linkedAssetKeys[$row['itemtype'] . ':' . $row['items_id']] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$client = new UrbackupApiClient($server);
|
|
||||||
$urbackupClients = $client->getStatus();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo '<div class="alert alert-danger">';
|
|
||||||
echo htmlspecialchars($e->getMessage());
|
|
||||||
echo '</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$urbackupClientNames = [];
|
|
||||||
foreach ($urbackupClients as $uc) {
|
|
||||||
$name = strtolower((string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? ''));
|
|
||||||
if ($name !== '') {
|
|
||||||
$urbackupClientNames[$name] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$cacheEntity = [];
|
|
||||||
$cacheLocation = [];
|
|
||||||
$cacheState = [];
|
|
||||||
$cacheUser = [];
|
|
||||||
$cacheGroup = [];
|
|
||||||
|
|
||||||
$itemtypes = Config::getEnabledItemtypes();
|
|
||||||
$missingAssets = [];
|
|
||||||
$candidatesByType = [];
|
|
||||||
$totalAssets = 0;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
$totalAssets++;
|
|
||||||
$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);
|
|
||||||
|
|
||||||
$missingAssets[] = [
|
|
||||||
'itemtype' => $itemtype,
|
|
||||||
'items_id' => $assetId,
|
|
||||||
'name' => $name,
|
|
||||||
'entity' => $entityName,
|
|
||||||
'location' => $locationName,
|
|
||||||
'otherserial' => (string) ($assetRow['otherserial'] ?? ''),
|
|
||||||
'state' => $stateName,
|
|
||||||
'user' => $userName,
|
|
||||||
];
|
|
||||||
$candidatesByType[$itemtype][] = $assetId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$batchIps = self::batchLoadIps($candidatesByType);
|
|
||||||
$batchGroups = self::batchLoadGroups($candidatesByType);
|
|
||||||
|
|
||||||
foreach ($missingAssets as &$asset) {
|
|
||||||
$key = $asset['itemtype'] . ':' . $asset['items_id'];
|
|
||||||
$asset['ip'] = $batchIps[$key] ?? '';
|
|
||||||
$groupId = $batchGroups[$key] ?? 0;
|
|
||||||
$asset['group'] = $groupId > 0
|
|
||||||
? self::getCachedName('Group', $groupId, $cacheGroup)
|
|
||||||
: '';
|
|
||||||
}
|
|
||||||
unset($asset);
|
|
||||||
|
|
||||||
if (count($missingAssets) === 0) {
|
|
||||||
if ($totalAssets === 0) {
|
|
||||||
echo '<div class="alert alert-info">';
|
|
||||||
echo htmlspecialchars(__('No assets', 'urbackup'));
|
|
||||||
echo '</div>';
|
|
||||||
} else {
|
|
||||||
echo '<div class="alert alert-success">';
|
|
||||||
echo htmlspecialchars(__('All assets in this location are linked or already on the UrBackup server.', 'urbackup'));
|
|
||||||
echo '</div>';
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo '<div class="d-flex justify-content-end mb-2">';
|
|
||||||
echo '<input type="text" id="missing-search" class="form-control form-control-sm" placeholder="' . htmlspecialchars(__('Search...', 'urbackup')) . '" style="width:250px">';
|
|
||||||
echo '</div>';
|
|
||||||
|
|
||||||
$formAction = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php';
|
|
||||||
|
|
||||||
echo '<table id="missing-table" class="table table-striped table-hover">';
|
|
||||||
echo '<thead><tr>';
|
|
||||||
echo '<th class="sortable" data-col="0">' . htmlspecialchars(__('Name')) . ' <span class="sort-arrow"></span></th>';
|
|
||||||
echo '<th class="sortable" data-col="1">' . htmlspecialchars(Entity::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
|
|
||||||
echo '<th class="sortable" data-col="2">' . htmlspecialchars(Location::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
|
|
||||||
echo '<th class="sortable" data-col="3">' . htmlspecialchars(__('Inventory number')) . ' <span class="sort-arrow"></span></th>';
|
|
||||||
echo '<th class="sortable" data-col="4">' . htmlspecialchars(__('IP address', 'urbackup')) . ' <span class="sort-arrow"></span></th>';
|
|
||||||
echo '<th class="sortable" data-col="5">' . htmlspecialchars(State::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
|
|
||||||
echo '<th class="sortable" data-col="6">' . htmlspecialchars(User::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
|
|
||||||
echo '<th class="sortable" data-col="7">' . htmlspecialchars(Group::getTypeName(1)) . ' <span class="sort-arrow"></span></th>';
|
|
||||||
if ($canWrite) {
|
|
||||||
echo '<th data-col="8">' . htmlspecialchars(__('Actions', 'urbackup')) . '</th>';
|
|
||||||
}
|
|
||||||
echo '</tr></thead>';
|
|
||||||
echo '<tbody>';
|
|
||||||
|
|
||||||
foreach ($missingAssets as $asset) {
|
|
||||||
$item = new $asset['itemtype']();
|
|
||||||
$itemLink = '';
|
|
||||||
if ($item instanceof CommonDBTM && $item->getFromDB($asset['items_id'])) {
|
|
||||||
$itemLink = $item->getLinkURL();
|
|
||||||
}
|
|
||||||
|
|
||||||
echo '<tr>';
|
|
||||||
echo '<td>';
|
|
||||||
if ($itemLink !== '') {
|
|
||||||
echo '<a href="' . htmlspecialchars($itemLink) . '">' . htmlspecialchars($asset['name']) . '</a>';
|
|
||||||
} else {
|
|
||||||
echo htmlspecialchars($asset['name']);
|
|
||||||
}
|
|
||||||
echo '</td>';
|
|
||||||
echo '<td>' . htmlspecialchars($asset['entity']) . '</td>';
|
|
||||||
echo '<td>' . htmlspecialchars($asset['location']) . '</td>';
|
|
||||||
echo '<td>' . htmlspecialchars($asset['otherserial']) . '</td>';
|
|
||||||
echo '<td>' . htmlspecialchars($asset['ip']) . '</td>';
|
|
||||||
echo '<td>' . htmlspecialchars($asset['state']) . '</td>';
|
|
||||||
echo '<td>' . htmlspecialchars($asset['user']) . '</td>';
|
|
||||||
echo '<td>' . htmlspecialchars($asset['group']) . '</td>';
|
|
||||||
if ($canWrite) {
|
|
||||||
echo '<td>';
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Batch-load IPs for multiple assets across itemtypes.
|
|
||||||
*
|
|
||||||
* @param array<string, list<int>> $candidatesByType itemtype => [items_id, ...]
|
|
||||||
*
|
|
||||||
* @return array<string, string> key "itemtype:items_id" => IP
|
|
||||||
*/
|
|
||||||
private static function batchLoadIps(array $candidatesByType): array
|
|
||||||
{
|
|
||||||
global $DB;
|
|
||||||
|
|
||||||
$ips = [];
|
|
||||||
|
|
||||||
foreach ($candidatesByType as $itemtype => $ids) {
|
|
||||||
if (count($ids) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$iterator = $DB->request([
|
|
||||||
'SELECT' => ['np.items_id', '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' => $ids,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($iterator as $row) {
|
|
||||||
$ip = (string) ($row['name'] ?? '');
|
|
||||||
if ($ip === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$key = $itemtype . ':' . $row['items_id'];
|
|
||||||
if (!isset($ips[$key])) {
|
|
||||||
$ips[$key] = $ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ips;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Batch-load group IDs for multiple assets across itemtypes.
|
|
||||||
*
|
|
||||||
* @param array<string, list<int>> $candidatesByType itemtype => [items_id, ...]
|
|
||||||
*
|
|
||||||
* @return array<string, int> key "itemtype:items_id" => groups_id
|
|
||||||
*/
|
|
||||||
private static function batchLoadGroups(array $candidatesByType): array
|
|
||||||
{
|
|
||||||
global $DB;
|
|
||||||
|
|
||||||
$groups = [];
|
|
||||||
|
|
||||||
foreach ($candidatesByType as $itemtype => $ids) {
|
|
||||||
if (count($ids) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$iterator = $DB->request([
|
|
||||||
'FROM' => 'glpi_groups_items',
|
|
||||||
'WHERE' => [
|
|
||||||
'itemtype' => $itemtype,
|
|
||||||
'items_id' => $ids,
|
|
||||||
'type' => \Group_Item::GROUP_TYPE_NORMAL,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($iterator as $row) {
|
|
||||||
$key = $itemtype . ':' . $row['items_id'];
|
|
||||||
if (!isset($groups[$key])) {
|
|
||||||
$groups[$key] = (int) ($row['groups_id'] ?? 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getAssetGroupName(string $itemtype, int $items_id, array &$cache): string
|
|
||||||
{
|
|
||||||
global $DB;
|
|
||||||
|
|
||||||
$iterator = $DB->request([
|
|
||||||
'FROM' => 'glpi_groups_items',
|
|
||||||
'WHERE' => [
|
|
||||||
'itemtype' => $itemtype,
|
|
||||||
'items_id' => $items_id,
|
|
||||||
'type' => \Group_Item::GROUP_TYPE_NORMAL,
|
|
||||||
],
|
|
||||||
'LIMIT' => 1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($iterator as $row) {
|
|
||||||
$groupId = (int) ($row['groups_id'] ?? 0);
|
|
||||||
if ($groupId > 0) {
|
|
||||||
return self::getCachedName('Group', $groupId, $cache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getCachedLocationName(int $id, array &$cache): string
|
|
||||||
{
|
|
||||||
if ($id <= 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (!isset($cache[$id])) {
|
|
||||||
$obj = new Location();
|
|
||||||
if ($obj->getFromDB($id)) {
|
|
||||||
$cache[$id] = (string) ($obj->fields['completename'] ?? $obj->fields['name'] ?? '');
|
|
||||||
} else {
|
|
||||||
$cache[$id] = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $cache[$id];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getAssetIp(string $itemtype, int $items_id): string
|
|
||||||
{
|
|
||||||
global $DB;
|
|
||||||
|
|
||||||
$iterator = $DB->request([
|
|
||||||
'SELECT' => ['ipa.name'],
|
|
||||||
'FROM' => 'glpi_ipaddresses AS ipa',
|
|
||||||
'INNER JOIN' => [
|
|
||||||
'glpi_networknames AS nn' => [
|
|
||||||
'ON' => [
|
|
||||||
'nn' => 'items_id',
|
|
||||||
'ipa' => 'id',
|
|
||||||
['AND' => ['ipa.itemtype' => 'NetworkName']],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'glpi_networkports AS np' => [
|
|
||||||
'ON' => [
|
|
||||||
'np' => 'id',
|
|
||||||
'nn' => 'items_id',
|
|
||||||
['AND' => ['nn.itemtype' => 'NetworkPort']],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'WHERE' => [
|
|
||||||
'np.itemtype' => $itemtype,
|
|
||||||
'np.items_id' => $items_id,
|
|
||||||
],
|
|
||||||
'LIMIT' => 1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($iterator as $row) {
|
|
||||||
$ip = (string) ($row['name'] ?? '');
|
|
||||||
if ($ip !== '') {
|
|
||||||
return $ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+14
-6
@@ -79,6 +79,7 @@ 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);
|
||||||
|
|
||||||
@@ -87,6 +88,10 @@ 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'],
|
||||||
@@ -238,40 +243,43 @@ 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='4'>" . htmlspecialchars(__('Linked assets', 'urbackup')) . "</th></tr>";
|
echo "<tr><th colspan='5'>" . 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($asset_ip ?: '-') . "</td>";
|
echo "<td>" . htmlspecialchars((string) $row['client_ip']) . "</td>";
|
||||||
|
echo "<td>" . htmlspecialchars((string) $row['last_file_backup']) . "</td>";
|
||||||
|
echo "<td>" . htmlspecialchars((string) $row['last_image_backup']) . "</td>";
|
||||||
echo "</tr>";
|
echo "</tr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ class UrbackupApiClient
|
|||||||
|
|
||||||
private bool $ignore_ssl;
|
private bool $ignore_ssl;
|
||||||
|
|
||||||
private int $timeout = 30;
|
private int $timeout = 10;
|
||||||
|
|
||||||
private int $connect_timeout = 5;
|
|
||||||
|
|
||||||
private string $session = '';
|
private string $session = '';
|
||||||
|
|
||||||
@@ -37,10 +35,6 @@ class UrbackupApiClient
|
|||||||
|
|
||||||
private int $lastlogid = 0;
|
private int $lastlogid = 0;
|
||||||
|
|
||||||
private ?array $cached_status = null;
|
|
||||||
|
|
||||||
private array $cached_settings = [];
|
|
||||||
|
|
||||||
private string $server_version = '';
|
private string $server_version = '';
|
||||||
|
|
||||||
private bool $is_version_2_4_or_higher = false;
|
private bool $is_version_2_4_or_higher = false;
|
||||||
@@ -251,29 +245,21 @@ class UrbackupApiClient
|
|||||||
*/
|
*/
|
||||||
public function getStatus(): array
|
public function getStatus(): array
|
||||||
{
|
{
|
||||||
if ($this->cached_status !== null) {
|
|
||||||
return $this->cached_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $this->apiAction('status');
|
$data = $this->apiAction('status');
|
||||||
|
|
||||||
if (isset($data['status']) && is_array($data['status'])) {
|
if (isset($data['status']) && is_array($data['status'])) {
|
||||||
$this->cached_status = array_values($data['status']);
|
return array_values($data['status']);
|
||||||
return $this->cached_status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($data['clients']) && is_array($data['clients'])) {
|
if (isset($data['clients']) && is_array($data['clients'])) {
|
||||||
$this->cached_status = array_values($data['clients']);
|
return array_values($data['clients']);
|
||||||
return $this->cached_status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (array_is_list($data)) {
|
if (array_is_list($data)) {
|
||||||
$this->cached_status = $data;
|
return $data;
|
||||||
return $this->cached_status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cached_status = [];
|
return [];
|
||||||
return $this->cached_status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -329,22 +315,16 @@ class UrbackupApiClient
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->cached_settings[$client_id])) {
|
|
||||||
return $this->cached_settings[$client_id];
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $this->apiAction('settings', [
|
$data = $this->apiAction('settings', [
|
||||||
'sa' => 'clientsettings',
|
'sa' => 'clientsettings',
|
||||||
't_clientid' => $client_id,
|
't_clientid' => $client_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (isset($data['settings']) && is_array($data['settings'])) {
|
if (isset($data['settings']) && is_array($data['settings'])) {
|
||||||
$this->cached_settings[$client_id] = $data['settings'];
|
return $data['settings'];
|
||||||
return $this->cached_settings[$client_id];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cached_settings[$client_id] = $data;
|
return $data;
|
||||||
return $this->cached_settings[$client_id];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -356,11 +336,6 @@ class UrbackupApiClient
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function clearSettingsCache(int $client_id): void
|
|
||||||
{
|
|
||||||
unset($this->cached_settings[$client_id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function changeClientSetting(string $client_name, string $key, mixed $value): bool
|
public function changeClientSetting(string $client_name, string $key, mixed $value): bool
|
||||||
{
|
{
|
||||||
$client_id = $this->getClientIdByName($client_name);
|
$client_id = $this->getClientIdByName($client_name);
|
||||||
@@ -376,7 +351,6 @@ class UrbackupApiClient
|
|||||||
$key => (string) $value,
|
$key => (string) $value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->clearSettingsCache($client_id);
|
|
||||||
return $this->responseIsSuccess($data);
|
return $this->responseIsSuccess($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +369,6 @@ class UrbackupApiClient
|
|||||||
$key => $value,
|
$key => $value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->clearSettingsCache($client_id);
|
|
||||||
return $this->responseIsSuccess($data);
|
return $this->responseIsSuccess($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,7 +391,6 @@ class UrbackupApiClient
|
|||||||
|
|
||||||
$data = $this->apiAction('settings', $params);
|
$data = $this->apiAction('settings', $params);
|
||||||
|
|
||||||
$this->clearSettingsCache($client_id);
|
|
||||||
return $this->responseIsSuccess($data);
|
return $this->responseIsSuccess($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,7 +667,6 @@ class UrbackupApiClient
|
|||||||
$options = [
|
$options = [
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
CURLOPT_TIMEOUT => $this->timeout,
|
CURLOPT_TIMEOUT => $this->timeout,
|
||||||
CURLOPT_CONNECTTIMEOUT => $this->connect_timeout,
|
|
||||||
CURLOPT_SSL_VERIFYPEER => !$this->ignore_ssl,
|
CURLOPT_SSL_VERIFYPEER => !$this->ignore_ssl,
|
||||||
CURLOPT_SSL_VERIFYHOST => $this->ignore_ssl ? 0 : 2,
|
CURLOPT_SSL_VERIFYHOST => $this->ignore_ssl ? 0 : 2,
|
||||||
CURLOPT_HTTPHEADER => [
|
CURLOPT_HTTPHEADER => [
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<div class="plugin-urbackup-asset-tab">
|
||||||
|
{% if not linked %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{{ __('No UrBackup server linked.', 'urbackup') }}
|
||||||
|
</div>
|
||||||
|
<table class="tab_cadre_fixe">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">{{ __('UrBackup server selection', 'urbackup') }}</th>
|
||||||
|
</tr>
|
||||||
|
{% if is_sub_location %}
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td colspan="2"><em>
|
||||||
|
{{ __('The asset is in a sub-location. The plugin will use the server assigned to the root location.', 'urbackup') }}
|
||||||
|
</em></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if servers|length == 0 %}
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td colspan="2">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{{ __('No UrBackup server available for the root location of this asset.', 'urbackup') }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% elseif can_connect %}
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('Available servers for root location', 'urbackup') }}</td>
|
||||||
|
<td>
|
||||||
|
<form method="post" action="{{ path('urbackup_asset_action') }}">
|
||||||
|
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="itemtype" value="{{ itemtype }}">
|
||||||
|
<input type="hidden" name="items_id" value="{{ items_id }}">
|
||||||
|
<select name="plugin_urbackup_servers_id">
|
||||||
|
{% for id, name in servers %}
|
||||||
|
<option value="{{ id }}">{{ name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button type="submit" name="connect" class="btn btn-primary">{{ __('Connect', 'urbackup') }}</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td colspan="2">
|
||||||
|
{{ __('A server is available, but you do not have permission to link this asset.', 'urbackup') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<table class="tab_cadre_fixe">
|
||||||
|
<tr>
|
||||||
|
<th colspan="4">{{ __('UrBackup status', 'urbackup') }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('Linked server', 'urbackup') }}</td>
|
||||||
|
<td>{{ server_link }}</td>
|
||||||
|
<td>{{ __('IP address', 'urbackup') }}</td>
|
||||||
|
<td>{{ server_ip }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('UrBackup server version', 'urbackup') }}</td>
|
||||||
|
<td>{{ server_version }}</td>
|
||||||
|
<td>{{ __('Client name', 'urbackup') }}</td>
|
||||||
|
<td>{{ client_name }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<form method="post" action="{{ path('urbackup_config') }}">
|
||||||
|
<div class="center">
|
||||||
|
<table class="tab_cadre_fixe">
|
||||||
|
<tr>
|
||||||
|
<th colspan="3">{{ __('UrBackup configuration', 'urbackup') }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{{ __('Asset type', 'urbackup') }}</th>
|
||||||
|
<th>{{ __('Enabled', 'urbackup') }}</th>
|
||||||
|
<th>{{ __('Default', 'urbackup') }}</th>
|
||||||
|
</tr>
|
||||||
|
{% for itemtype, label in configurable_types %}
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ label }}</td>
|
||||||
|
{% if itemtype == 'Computer' %}
|
||||||
|
<td>{{ __('Always enabled', 'urbackup') }}</td>
|
||||||
|
{% else %}
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="assettypes[{{ itemtype }}]" value="1"{% if enabled_types[itemtype] %} checked{% endif %}>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
<td>
|
||||||
|
{% if itemtype == 'Computer' %}
|
||||||
|
{{ __('Yes', 'urbackup') }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="center">
|
||||||
|
<button type="submit" name="update" class="btn btn-primary">
|
||||||
|
{{ __('Save', 'urbackup') }}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}">
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
{% extends 'layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="center">
|
||||||
|
<form method="post" action="{{ path('urbackup_server_save') }}">
|
||||||
|
<table class="tab_cadre_fixe">
|
||||||
|
<tr>
|
||||||
|
<th colspan="4">{{ __('UrBackup server', 'urbackup') }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('Name') }}</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="name" value="{{ server.name ?? '' }}" size="40">
|
||||||
|
</td>
|
||||||
|
<td>{{ __('Active') }}</td>
|
||||||
|
<td>
|
||||||
|
<select name="is_active">
|
||||||
|
<option value="1"{% if server.is_active ?? true %} selected{% endif %}>{{ __('Yes') }}</option>
|
||||||
|
<option value="0"{% if not (server.is_active ?? true) %} selected{% endif %}>{{ __('No') }}</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('Entity') }}</td>
|
||||||
|
<td>
|
||||||
|
{{ render_entity_dropdown('entities_id', server.entities_id ?? 0) }}
|
||||||
|
</td>
|
||||||
|
<td>{{ __('Recursive') }}</td>
|
||||||
|
<td>
|
||||||
|
<select name="is_recursive">
|
||||||
|
<option value="1"{% if server.is_recursive ?? false %} selected{% endif %}>{{ __('Yes') }}</option>
|
||||||
|
<option value="0"{% if not (server.is_recursive ?? false) %} selected{% endif %}>{{ __('No') }}</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('Location') }}</td>
|
||||||
|
<td>
|
||||||
|
{{ render_location_dropdown('locations_id', server.locations_id ?? 0) }}
|
||||||
|
<br><small>{{ __('Associate the server with the main/root location. Assets in sub-locations will use this root location server.', 'urbackup') }}</small>
|
||||||
|
</td>
|
||||||
|
<td>{{ __('Protocol', 'urbackup') }}</td>
|
||||||
|
<td>
|
||||||
|
<select name="protocol">
|
||||||
|
<option value="http"{% if server.protocol == 'http' %} selected{% endif %}>HTTP</option>
|
||||||
|
<option value="https"{% if server.protocol == 'https' %} selected{% endif %}>HTTPS</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('IP address', 'urbackup') }}</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="ip_address" value="{{ server.ip_address ?? '' }}" size="40">
|
||||||
|
</td>
|
||||||
|
<td>{{ __('Network port', 'urbackup') }}</td>
|
||||||
|
<td>
|
||||||
|
<input type="number" name="port" value="{{ server.port ?? 55414 }}" min="1" max="65535">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('UrBackup server version', 'urbackup') }}</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="server_version" value="{{ server.server_version ?? '' }}" size="30">
|
||||||
|
</td>
|
||||||
|
<td>{{ __('Ignore SSL verification', 'urbackup') }}</td>
|
||||||
|
<td>
|
||||||
|
<select name="ignore_ssl">
|
||||||
|
<option value="1"{% if server.ignore_ssl ?? false %} selected{% endif %}>{{ __('Yes') }}</option>
|
||||||
|
<option value="0"{% if not (server.ignore_ssl ?? false) %} selected{% endif %}>{{ __('No') }}</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('API username', 'urbackup') }}</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="api_username" value="{{ server.api_username ?? '' }}" size="40" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
<td>{{ __('API password', 'urbackup') }}</td>
|
||||||
|
<td>
|
||||||
|
<input type="password" name="api_password" value="{{ server.api_password ?? '' }}" autocomplete="new-password">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('Comments') }}</td>
|
||||||
|
<td colspan="3">
|
||||||
|
<textarea name="comment" rows="5" cols="100">{{ server.comment ?? '' }}</textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% if server.id > 0 %}
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('UrBackup web interface', 'urbackup') }}</td>
|
||||||
|
<td colspan="3">
|
||||||
|
{% if server.url %}
|
||||||
|
<a href="{{ server.url }}" target="_blank" rel="noopener" class="btn btn-secondary">
|
||||||
|
{{ __('Open UrBackup interface', 'urbackup') }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ __('No URL available', 'urbackup') }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ __('API test', 'urbackup') }}</td>
|
||||||
|
<td colspan="3">
|
||||||
|
<button type="button" class="btn btn-primary plugin-urbackup-test-api" data-server-id="{{ server.id }}">
|
||||||
|
{{ __('Test API connection', 'urbackup') }}
|
||||||
|
</button>
|
||||||
|
<span id="plugin-urbackup-api-test-result"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
<input type="hidden" name="id" value="{{ server.id ?? 0 }}">
|
||||||
|
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}">
|
||||||
|
<button type="submit" name="save" class="btn btn-primary">{{ __('Save') }}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
{% extends 'layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="center">
|
||||||
|
<table class="tab_cadre_fixe">
|
||||||
|
<tr>
|
||||||
|
<th colspan="6">{{ __('UrBackup servers', 'urbackup') }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{{ __('Name') }}</th>
|
||||||
|
<th>{{ __('IP address', 'urbackup') }}</th>
|
||||||
|
<th>{{ __('Port') }}</th>
|
||||||
|
<th>{{ __('Version') }}</th>
|
||||||
|
<th>{{ __('Status') }}</th>
|
||||||
|
<th>{{ __('Actions') }}</th>
|
||||||
|
</tr>
|
||||||
|
{% for server in servers %}
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td>{{ server.name }}</td>
|
||||||
|
<td>{{ server.ip_address }}</td>
|
||||||
|
<td>{{ server.port }}</td>
|
||||||
|
<td>{{ server.server_version }}</td>
|
||||||
|
<td>
|
||||||
|
{% if server.last_api_status %}
|
||||||
|
<span class="badge bg-success">{{ __('OK', 'urbackup') }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-danger">{{ __('Failed', 'urbackup') }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ path('urbackup_server_show', {'id': server.id}) }}" class="btn btn-sm btn-primary">
|
||||||
|
{{ __('View') }}
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-sm btn-secondary plugin-urbackup-test-api" data-server-id="{{ server.id }}">
|
||||||
|
{{ __('Test API', 'urbackup') }}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr class="tab_bg_1">
|
||||||
|
<td colspan="6" class="center">{{ __('No records found', 'urbackup') }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% if can_create %}
|
||||||
|
<a href="{{ path('urbackup_server_show', {'id': 0}) }}" class="btn btn-success">
|
||||||
|
{{ __('Add') }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user