Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9582554dfe | |||
| 906f308f50 | |||
| 7fb79c9552 | |||
| c90506a25d | |||
| 425f8cb5a4 | |||
| 0bdd5476cb | |||
| 41a8f8dbb4 | |||
| 13cb325576 | |||
| f7c1ab8aa4 | |||
| f0a5be45ee | |||
| f0eaa76f17 | |||
| 223180d1a9 | |||
| 5fac26040a | |||
| 9ee28330ac | |||
| 8a52489b41 | |||
| 9979d62a2a | |||
| 01d678e855 | |||
| 0e80c1430f | |||
| d69c465c92 | |||
| aba2ecf4aa | |||
| df28fc0018 | |||
| 7a578db677 | |||
| 771409e396 | |||
| 6441a8636d | |||
| 6a49489b73 | |||
| f4bd50c1b1 |
@@ -1,66 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/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,269 +1,94 @@
|
||||
# UrBackup Plugin - Memoria di Sviluppo
|
||||
# MEMORY.md - Stato del Plugin UrBackup
|
||||
|
||||
## 📋 Informazioni Plugin
|
||||
## Ultima modifica: 28/05/2026
|
||||
|
||||
- **Nome**: UrBackup
|
||||
- **Versione**: 0.4.2
|
||||
- **Compatibilità**: GLPI 11.0.6+, PHP 8.3+
|
||||
- **Namespace**: `GlpiPlugin\Urbackup`
|
||||
## GLPI 11 — Session::checkCSRF() breaking change
|
||||
In GLPI 11, `Session::checkCSRF()` richiede il primo argomento `$data` (i dati POST da validare). In GLPI ≤10 accettava zero argomenti.
|
||||
Il listener globale `CheckCsrfListener` gestisce già il CSRF per tutte le richieste POST, ma il plugin chiamava `Session::checkCSRF()` senza argomenti causando errore fatale.
|
||||
|
||||
## ✅ Funzionalità Implementate
|
||||
### Fix applicati (tutti i file)
|
||||
- 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
|
||||
|
||||
### 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`
|
||||
## Performance - Asset Definition vs Computer
|
||||
|
||||
### Struttura File
|
||||
- `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`)
|
||||
### Problema
|
||||
Gli Asset Definition custom (GLPI 11) sono 2-5x più lenti dei Computer nativi nel caricamento del tab UrBackup.
|
||||
|
||||
### Classi Principali
|
||||
| Classe | Descrizione |
|
||||
|--------|-------------|
|
||||
| `Config` | Configurazione plugin, tipi asset abilitati |
|
||||
| `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 |
|
||||
### Causa
|
||||
L'overhead è in GLPI 11 core, non nel plugin:
|
||||
1. **`Asset::__construct()`** — itera 30+ Capacity classi per ogni istanza
|
||||
2. **`Asset::post_getFromDB()`** — decodifica JSON custom fields e li processa
|
||||
3. **`eval()` autoloading** — le classi concrete sono definite via `eval()` a runtime
|
||||
|
||||
### Pagine Frontend
|
||||
- `front/config.form.php`: Pagina configurazione plugin
|
||||
- `front/server.php`: Lista server UrBackup
|
||||
- `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
|
||||
### Ottimizzazioni applicate
|
||||
1. **`AssetTab.php::loadApiData()`** — letto `$item->fields['name']` direttamente invece di chiamare `ServerAsset::getAssetName()` che faceva una seconda `getFromDB()` ridondante
|
||||
2. **`AssetTab.php::startBackup()`, `saveInternetMode()`, `saveDefaultDirs()`, `showServerLinkedBlock()`** — stesso pattern, `$item->fields['name']` al posto di `getAssetName()`
|
||||
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)
|
||||
|
||||
### Interfacce GLPI
|
||||
- ✅ Menu: Amministrazione → Server UrBackup
|
||||
- ✅ Tab: UrBackup su Computer e asset abilitati
|
||||
- ✅ Configurazione: Abilitazione tipi asset (legacy + Asset Definition GLPI 11)
|
||||
## Architettura
|
||||
- `src/Capacity/UrBackupCapacity.php` — registra `AssetTab` via `CommonGLPI::registerStandardTab()` in `onClassBootstrap()`
|
||||
- `setup.php` — registra capacità, CSS, JS, hook
|
||||
- `src/AssetTab.php` — display tab content + tab interni (Stato/Azioni/Info-Log)
|
||||
- `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
|
||||
|
||||
## 🔧 Fix e Correzioni Applicate
|
||||
## Asset Tab Interni
|
||||
- 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)
|
||||
|
||||
1. **OPcache**: Funzioni install in setup.php (non in hook.php) per problema OPcache
|
||||
2. **Duplicate profilerights**: Aggiunto check in `Profile::registerRights()` per evitare duplicati
|
||||
3. **CSRF**: Include `inc/includes.php` nei file front per gestione CSRF
|
||||
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.
|
||||
## Cache
|
||||
- `UrbackupApiClient`: cache in-memory per `getStatus()` e `getClientSettings()`
|
||||
- `AssetTab::loadApiData()`: cache sessione 30s (chiave: server_id + client_name)
|
||||
- API timeout: 30s, connect timeout: 5s
|
||||
|
||||
### Modifica Test API Connection (v0.4.3)
|
||||
## Bugfix: Campi API username/password non visibili in nuova installazione
|
||||
|
||||
**Data**: 2026-05-19
|
||||
### Problema
|
||||
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.
|
||||
|
||||
**Descrizione**: Modificato il comportamento del test API nel form del server:
|
||||
- Rimosso il pulsante "Test API Connection" manuale
|
||||
- Rimosse le righe "Last API status", "Last API message", "Last API check"
|
||||
- Aggiunto controllo automatico della connessione API eseguito ad ogni visualizzazione del form
|
||||
### Cause (2 bug distinti)
|
||||
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.
|
||||
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.
|
||||
|
||||
**Stati visualizzati**:
|
||||
- 🟢 **Verde** "Connessione API OK" - Connessione riuscita
|
||||
- 🔴 **Rosso** "Connessione API fallita" - Errore di autenticazione o configurazione
|
||||
- 🟡 **Giallo** "Server irraggiungibile" - Problemi di rete (timeout, DNS, HTTP 4xx/5xx, SSL)
|
||||
### Fix applicati
|
||||
1. **`Server.php::showFormFields()`** — Sostituito `$canUpdate` con `$canEdit`: per nuovi server (`$ID <= 0`) usa `CREATE`, per server esistenti usa `UPDATE`.
|
||||
2. **`Profile.php::installRights()`** — In assenza di sessione attiva (CLI), cerca il profilo "Super-Admin" tramite query diretta e gli assegna tutti i diritti.
|
||||
|
||||
**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
|
||||
## Bugfix: Unknown column 'users_id' — Group::getDataItems() SQL error
|
||||
|
||||
### API UrBackup - Endpoints Verificati
|
||||
| Endpoint | Metodo | Note |
|
||||
|----------|--------|------|
|
||||
| `/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
|
||||
### Problema
|
||||
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:
|
||||
```sql
|
||||
WHERE ((... ) OR (`users_id` IN (SELECT `users_id` FROM `glpi_groups_users` WHERE `groups_id` IN ('7483'))))
|
||||
```
|
||||
La colonna `users_id` non esisteva nella tabella `glpi_plugin_urbackup_servers`.
|
||||
|
||||
## 📚 Riferimenti
|
||||
### Causa
|
||||
`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.
|
||||
|
||||
- GLPI 11 Plugin Development: https://glpi-project.org/documentation/
|
||||
- Plugin Example: https://github.com/pluginsGLPI/example
|
||||
- GLPI Inventory: https://github.com/glpi-project/glpi-inventory-plugin
|
||||
### Fix
|
||||
1. **`install/mysql/plugin_urbackup-empty.sql`** — Aggiunta colonna `users_id INT UNSIGNED NOT NULL DEFAULT 0` dopo `locations_id` + indice
|
||||
2. **`install/install.php`** — Aggiunto `$migration->addField('users_id', ...)` e `$migration->addKey('users_id')` nella migrazione del server
|
||||
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
|
||||
|
||||
## 🔌 API UrBackup - Riferimenti Ufficiali
|
||||
|
||||
### Documentazione UrBackup Web API
|
||||
|
||||
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`).
|
||||
|
||||
### Risorse API Utilizzate
|
||||
|
||||
1. **Python Wrapper** (uroni/urbackup-server-python-web-api-wrapper)
|
||||
- URL: https://github.com/uroni/urbackup-server-python-web-api-wrapper
|
||||
- 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
|
||||
## 0.7.0 — Pulizia, sicurezza e DB cleanup
|
||||
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/)
|
||||
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
|
||||
5. **SQL schema pulito**: Rimosse tabelle legacy dall'empty.sql
|
||||
6. **PHP lint warnings**: Rimosse `use Html;` e `use Search;` superflue
|
||||
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
|
||||
9. **CSRF GLPI 11 fix**: `Session::checkCSRF()` richiede `$_POST` come argomento in GLPI 11; aggiunto `X-Glpi-Csrf-Token` header al JS AJAX
|
||||
10. **Session::isDebugActive()**: Metodo inesistente in GLPI 11; sostituito in `AssetTab.php:150` con `($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE`
|
||||
|
||||
@@ -1,37 +1,130 @@
|
||||
# UrBackup Plugin for GLPI 11
|
||||
# UrBackup plugin for GLPI 11
|
||||
|
||||
Plugin **UrBackup** per GLPI 11, sviluppato in PHP 8.3, che consente la gestione
|
||||
centralizzata dei server UrBackup e dei client direttamente dall’interfaccia GLPI.
|
||||
[](https://glpi-project.org)
|
||||
[](https://php.net)
|
||||
[](LICENSE)
|
||||
|
||||
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).
|
||||
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.
|
||||
|
||||
---
|
||||
## Features
|
||||
|
||||
## ✅ Compatibilità
|
||||
- **Server management** – Register UrBackup servers by location; test API connectivity.
|
||||
- **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.
|
||||
|
||||
| Componente | Versione |
|
||||
|-----------|----------|
|
||||
| GLPI | 11.x |
|
||||
| PHP | ≥ 8.3 |
|
||||
| Database | MySQL / MariaDB (GLPI standard) |
|
||||
## Requirements
|
||||
|
||||
---
|
||||
| Component | Version |
|
||||
|-----------|---------|
|
||||
| **GLPI** | >= 11.0.6, < 12.0.0 |
|
||||
| **PHP** | >= 8.3.0 (8.4 supported) |
|
||||
| **Database** | MySQL 5.7+ / MariaDB 10.5+ |
|
||||
|
||||
## ✅ Funzionalità principali
|
||||
## Installation
|
||||
|
||||
### 🔧 Configurazione plugin
|
||||
- Percorso: **Configurazione → Plugin → UrBackup**
|
||||
- Asset supportati:
|
||||
- Computer (sempre attivo)
|
||||
- Altri asset GLPI configurabili
|
||||
- Attivazione automatica tab e massive action sugli asset abilitati
|
||||
1. **Download** the plugin and extract it to `GLPI_ROOT/plugins/urbackup/`
|
||||
2. **Install** via GLPI web interface or CLI:
|
||||
|
||||
---
|
||||
```bash
|
||||
php bin/console glpi:plugin:install urbackup
|
||||
php bin/console glpi:plugin:activate urbackup
|
||||
```
|
||||
|
||||
### 🗄️ Gestione server UrBackup
|
||||
3. **Configure rights** – Go to *Administration > Profiles*, select a profile, and set *UrBackup* rights (READ / UPDATE / CREATE / DELETE / PURGE).
|
||||
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).
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<?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": "finstral/glpi-urbackup-plugin",
|
||||
"name": "glpi/urbackup-plugin",
|
||||
"description": "UrBackup integration plugin for GLPI 11",
|
||||
"type": "glpi-plugin",
|
||||
"license": "GPL-2.0-or-later",
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<?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()]);
|
||||
}
|
||||
+30
-15
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------------
|
||||
* UrBackup plugin for GLPI
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
use Glpi\Exception\Http\BadRequestHttpException;
|
||||
use GlpiPlugin\Urbackup\AssetTab;
|
||||
use GlpiPlugin\Urbackup\Config;
|
||||
use GlpiPlugin\Urbackup\Profile;
|
||||
@@ -25,11 +28,11 @@ $itemtype = (string) ($_POST['itemtype'] ?? $_GET['itemtype'] ?? '');
|
||||
$items_id = (int) ($_POST['items_id'] ?? $_GET['items_id'] ?? 0);
|
||||
|
||||
if ($itemtype === '' || $items_id <= 0) {
|
||||
Html::displayValidationError(__('Invalid parameters'));
|
||||
throw new BadRequestHttpException(__('Invalid parameters', 'urbackup'));
|
||||
}
|
||||
|
||||
if (!in_array($itemtype, Config::getEnabledItemtypes(), true)) {
|
||||
Html::displayValidationError(__('Item type not enabled for UrBackup'));
|
||||
throw new BadRequestHttpException(__('Item type not enabled for UrBackup', 'urbackup'));
|
||||
}
|
||||
|
||||
$item = getItemForItemtype($itemtype);
|
||||
@@ -46,13 +49,23 @@ if (isset($_POST['connect'])) {
|
||||
$server_id = (int) ($_POST['plugin_urbackup_servers_id'] ?? 0);
|
||||
|
||||
if ($server_id <= 0) {
|
||||
Html::displayValidationError(__('No server selected'));
|
||||
Session::addMessageAfterRedirect(
|
||||
__('No server selected', 'urbackup'),
|
||||
false,
|
||||
ERROR
|
||||
);
|
||||
Html::redirect($item->getLinkURL());
|
||||
}
|
||||
|
||||
$link = ServerAsset::getLinkForAsset($itemtype, $items_id);
|
||||
|
||||
if ($link !== null) {
|
||||
Html::displayValidationError(__('Asset is already linked to a server'));
|
||||
Session::addMessageAfterRedirect(
|
||||
__('Asset is already linked to a server', 'urbackup'),
|
||||
false,
|
||||
ERROR
|
||||
);
|
||||
Html::redirect($item->getLinkURL());
|
||||
}
|
||||
|
||||
$result = ServerAsset::createForAsset($itemtype, $items_id, $server_id);
|
||||
@@ -61,7 +74,12 @@ if (isset($_POST['connect'])) {
|
||||
$item->getFromDB($items_id);
|
||||
Html::redirect($item->getLinkURL());
|
||||
} else {
|
||||
Html::displayValidationError(__('Failed to link asset to server'));
|
||||
Session::addMessageAfterRedirect(
|
||||
__('Failed to link asset to server', 'urbackup'),
|
||||
false,
|
||||
ERROR
|
||||
);
|
||||
Html::redirect($item->getLinkURL());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,21 +88,18 @@ if (isset($_POST['disconnect'])) {
|
||||
Html::displayRightError();
|
||||
}
|
||||
|
||||
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']]);
|
||||
$result = ServerAsset::disconnectAsset($itemtype, $items_id);
|
||||
|
||||
if ($result) {
|
||||
$item->getFromDB($items_id);
|
||||
Html::redirect($item->getLinkURL());
|
||||
} else {
|
||||
Html::displayValidationError(__('Failed to disconnect asset from server'));
|
||||
Session::addMessageAfterRedirect(
|
||||
__('Failed to disconnect asset from server', 'urbackup'),
|
||||
false,
|
||||
ERROR
|
||||
);
|
||||
Html::redirect($item->getLinkURL());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use GlpiPlugin\Urbackup\Config;
|
||||
use Html;
|
||||
|
||||
global $CFG_GLPI;
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<?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);
|
||||
+112
-21
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use GlpiPlugin\Urbackup\Profile;
|
||||
use GlpiPlugin\Urbackup\Server;
|
||||
use GlpiPlugin\Urbackup\ServerAsset;
|
||||
use Html;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
define('GLPI_ROOT', dirname(__DIR__, 4));
|
||||
@@ -11,7 +12,7 @@ if (!defined('GLPI_ROOT')) {
|
||||
|
||||
include_once GLPI_ROOT . "/inc/includes.php";
|
||||
|
||||
if (!Profile::canCurrentUser(UPDATE)) {
|
||||
if (!Profile::canCurrentUser(READ)) {
|
||||
Html::displayRightError();
|
||||
}
|
||||
|
||||
@@ -48,30 +49,120 @@ $ID = $_GET['id'] ?? null;
|
||||
Html::header(
|
||||
$ID ? __('Edit UrBackup server', 'urbackup') : __('Add UrBackup server', 'urbackup'),
|
||||
'',
|
||||
'Assets',
|
||||
'admin',
|
||||
'GlpiPlugin\Urbackup\Server'
|
||||
);
|
||||
|
||||
$server->showForm($ID);
|
||||
if ($ID > 0) {
|
||||
global $DB;
|
||||
|
||||
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>';
|
||||
$server->getFromDB($ID);
|
||||
|
||||
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>';
|
||||
$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);
|
||||
}
|
||||
|
||||
?>
|
||||
<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();
|
||||
+5
-19
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use GlpiPlugin\Urbackup\Profile;
|
||||
use GlpiPlugin\Urbackup\Server;
|
||||
use Html;
|
||||
use Search;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
define('GLPI_ROOT', dirname(__DIR__, 4));
|
||||
@@ -15,27 +15,13 @@ if (!Profile::canCurrentUser(READ)) {
|
||||
Html::displayRightError();
|
||||
}
|
||||
|
||||
$can_read = Profile::canCurrentUser(READ);
|
||||
$can_create = Profile::canCurrentUser(CREATE);
|
||||
|
||||
Html::header(
|
||||
'UrBackup Servers',
|
||||
__('UrBackup Servers', 'urbackup'),
|
||||
'',
|
||||
'Assets',
|
||||
''
|
||||
'admin',
|
||||
'GlpiPlugin\Urbackup\Server'
|
||||
);
|
||||
|
||||
if ($can_create) {
|
||||
echo "<div class='center'>";
|
||||
echo Html::link(
|
||||
__('Add UrBackup server', 'urbackup'),
|
||||
'/plugins/urbackup/front/server.form.php',
|
||||
['class' => 'btn btn-primary']
|
||||
);
|
||||
echo "</div>";
|
||||
echo "<br>";
|
||||
}
|
||||
|
||||
Search::show('GlpiPlugin\Urbackup\Server', [
|
||||
'is_deleted' => 0,
|
||||
'massiveaction' => true,
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<?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()]);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?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,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* AJAX endpoint for testing UrBackup API
|
||||
*/
|
||||
@@ -11,21 +14,21 @@ Html::header_nocache();
|
||||
Session::checkLoginUser();
|
||||
|
||||
if (!Session::haveRight('plugin_urbackup', READ)) {
|
||||
echo json_encode(['success' => false, 'message' => 'No permission']);
|
||||
echo json_encode(['success' => false, 'message' => __('No permission', 'urbackup')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$server_id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||
|
||||
if ($server_id <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid server ID']);
|
||||
echo json_encode(['success' => false, 'message' => __('Invalid server ID', 'urbackup')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$server = new GlpiPlugin\Urbackup\Server();
|
||||
|
||||
if (!$server->getFromDB($server_id)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Server not found']);
|
||||
echo json_encode(['success' => false, 'message' => __('Server not found', 'urbackup')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?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();
|
||||
+83
-39
@@ -1,26 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------------
|
||||
* UrBackup plugin for GLPI
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* Install/update process for GLPI 11.
|
||||
*
|
||||
* GLPI 11 Migration class does not expose createTable()/addTable().
|
||||
* Compatible GLPI plugin pattern:
|
||||
*
|
||||
* - initial schema creation from SQL file using $DB->runFile()
|
||||
* - schema evolution using Migration addField(), addKey(), executeMigration()
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
use Glpi\Asset\AssetDefinitionManager;
|
||||
use GlpiPlugin\Urbackup\Capacity\UrBackupCapacity;
|
||||
use GlpiPlugin\Urbackup\Config;
|
||||
use GlpiPlugin\Urbackup\Profile;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die('Sorry. You cannot access this file directly.');
|
||||
die(__('Sorry. You cannot access this file directly.', 'urbackup'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,10 +31,15 @@ function plugin_urbackup_install_process(): bool
|
||||
plugin_urbackup_install_create_initial_schema($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_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();
|
||||
|
||||
@@ -63,10 +62,8 @@ function plugin_urbackup_install_create_initial_schema(Migration $migration): vo
|
||||
|
||||
$required_tables = [
|
||||
'glpi_plugin_urbackup_configs',
|
||||
'glpi_plugin_urbackup_assettypes',
|
||||
'glpi_plugin_urbackup_servers',
|
||||
'glpi_plugin_urbackup_serverassets',
|
||||
'glpi_plugin_urbackup_profiles',
|
||||
];
|
||||
|
||||
$missing_table_found = false;
|
||||
@@ -161,11 +158,6 @@ function plugin_urbackup_install_update_assettypes_table(Migration $migration):
|
||||
'after' => 'itemtype',
|
||||
]);
|
||||
|
||||
$migration->addField($table, 'is_default', 'bool', [
|
||||
'value' => 0,
|
||||
'after' => 'is_active',
|
||||
]);
|
||||
|
||||
$migration->addField($table, 'date_creation', 'timestamp');
|
||||
$migration->addField($table, 'date_mod', 'timestamp');
|
||||
|
||||
@@ -210,6 +202,11 @@ function plugin_urbackup_install_update_servers_table(Migration $migration): voi
|
||||
'after' => 'name',
|
||||
]);
|
||||
|
||||
$migration->addField($table, 'users_id', 'integer', [
|
||||
'value' => 0,
|
||||
'after' => 'locations_id',
|
||||
]);
|
||||
|
||||
$migration->addField($table, 'ip_address', 'string', [
|
||||
'value' => '',
|
||||
'after' => 'locations_id',
|
||||
@@ -270,7 +267,9 @@ function plugin_urbackup_install_update_servers_table(Migration $migration): voi
|
||||
$migration->addKey($table, 'name');
|
||||
$migration->addKey($table, 'entities_id');
|
||||
$migration->addKey($table, 'locations_id');
|
||||
$migration->addKey($table, 'users_id');
|
||||
$migration->addKey($table, 'is_active');
|
||||
$migration->addKey($table, ['locations_id', 'is_active'], 'location_active');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,44 +304,89 @@ function plugin_urbackup_install_update_serverassets_table(Migration $migration)
|
||||
'after' => 'itemtype',
|
||||
]);
|
||||
|
||||
$migration->addField($table, 'date_creation', 'timestamp');
|
||||
$migration->addField($table, 'date_mod', 'timestamp');
|
||||
|
||||
$migration->addKey($table, 'plugin_urbackup_servers_id');
|
||||
$migration->addKey($table, ['itemtype', 'items_id'], 'item');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update profiles table.
|
||||
* Drop legacy tables that are no longer used.
|
||||
*
|
||||
* @param Migration $migration Migration instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function plugin_urbackup_install_update_profiles_table(Migration $migration): void
|
||||
function plugin_urbackup_install_drop_legacy_tables(Migration $migration): void
|
||||
{
|
||||
global $DB;
|
||||
|
||||
// glpi_plugin_urbackup_profiles was replaced by Profile::registerRights() in 0.5.0
|
||||
$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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$migration->addField($table, 'profiles_id', 'integer', [
|
||||
'value' => 0,
|
||||
'after' => 'id',
|
||||
]);
|
||||
// Check if is_default column exists
|
||||
$has_is_default = false;
|
||||
foreach ($DB->listFields($table) as $col) {
|
||||
if ($col['Field'] === 'is_default') {
|
||||
$has_is_default = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$migration->addField($table, 'rightname', 'string', [
|
||||
'value' => '',
|
||||
'after' => 'profiles_id',
|
||||
]);
|
||||
// Auto-enable UrBackup capacity on ALL Asset Definitions
|
||||
// so the tab appears out of the box for any asset type.
|
||||
if (class_exists(AssetDefinitionManager::class)) {
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$migration->addField($table, 'rights', 'integer', [
|
||||
'value' => 0,
|
||||
'after' => 'rightname',
|
||||
]);
|
||||
|
||||
$migration->addField($table, 'date_creation', 'timestamp');
|
||||
$migration->addField($table, 'date_mod', 'timestamp');
|
||||
|
||||
$migration->addKey($table, ['profiles_id', 'rightname'], 'profile_right');
|
||||
// Drop is_default column (no longer needed in capacity system)
|
||||
if ($has_is_default) {
|
||||
$migration->dropField($table, 'is_default');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,13 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_configs` (
|
||||
KEY `name` (`name`)
|
||||
) 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` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`entities_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`is_recursive` TINYINT NOT NULL DEFAULT 0,
|
||||
`name` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`locations_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`users_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`ip_address` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`port` INT UNSIGNED NOT NULL DEFAULT 55414,
|
||||
`protocol` VARCHAR(10) NOT NULL DEFAULT 'http',
|
||||
@@ -44,7 +33,9 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_servers` (
|
||||
KEY `name` (`name`),
|
||||
KEY `entities_id` (`entities_id`),
|
||||
KEY `locations_id` (`locations_id`),
|
||||
KEY `is_active` (`is_active`)
|
||||
KEY `users_id` (`users_id`),
|
||||
KEY `is_active` (`is_active`),
|
||||
KEY `location_active` (`locations_id`, `is_active`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_serverassets` (
|
||||
@@ -52,18 +43,11 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_serverassets` (
|
||||
`plugin_urbackup_servers_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`itemtype` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`items_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`date_creation` TIMESTAMP NULL DEFAULT NULL,
|
||||
`date_mod` TIMESTAMP NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `plugin_urbackup_servers_id` (`plugin_urbackup_servers_id`),
|
||||
KEY `item` (`itemtype`, `items_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||
|
||||
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;
|
||||
-- glpi_plugin_urbackup_profiles was dropped in 0.7.0 (replaced by Profile::registerRights())
|
||||
@@ -1,18 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------------
|
||||
* UrBackup plugin for GLPI
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* Uninstall process for GLPI 11.
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
use GlpiPlugin\Urbackup\Profile;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die('Sorry. You cannot access this file directly.');
|
||||
die(__('Sorry. You cannot access this file directly.', 'urbackup'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* 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,3 +156,57 @@ msgstr "Letztes Backup"
|
||||
|
||||
msgid "Show"
|
||||
msgstr "Anzeigen"
|
||||
|
||||
msgid "HTTP"
|
||||
msgstr "HTTP"
|
||||
|
||||
msgid "HTTPS"
|
||||
msgstr "HTTPS"
|
||||
|
||||
msgid "API Error"
|
||||
msgstr "API-Fehler"
|
||||
|
||||
msgid "Unknown"
|
||||
msgstr "Unbekannt"
|
||||
|
||||
msgid "Asset Definition"
|
||||
msgstr "Asset-Definition"
|
||||
|
||||
msgid "Legacy"
|
||||
msgstr "Legacy"
|
||||
|
||||
msgid "UrBackup Servers"
|
||||
msgstr "UrBackup-Server"
|
||||
|
||||
msgid "Invalid parameters"
|
||||
msgstr "Ungültige Parameter"
|
||||
|
||||
msgid "Item type not enabled for UrBackup"
|
||||
msgstr "Elementtyp für UrBackup nicht aktiviert"
|
||||
|
||||
msgid "No server selected"
|
||||
msgstr "Kein Server ausgewählt"
|
||||
|
||||
msgid "Asset is already linked to a server"
|
||||
msgstr "Asset ist bereits mit einem Server verknüpft"
|
||||
|
||||
msgid "Failed to link asset to server"
|
||||
msgstr "Verknüpfung von Asset mit Server fehlgeschlagen"
|
||||
|
||||
msgid "Failed to disconnect asset from server"
|
||||
msgstr "Trennung von Asset vom Server fehlgeschlagen"
|
||||
|
||||
msgid "No permission"
|
||||
msgstr "Keine Berechtigung"
|
||||
|
||||
msgid "Invalid server ID"
|
||||
msgstr "Ungültige Server-ID"
|
||||
|
||||
msgid "Server not found"
|
||||
msgstr "Server nicht gefunden"
|
||||
|
||||
msgid "Sorry. You cannot access this file directly."
|
||||
msgstr "Entschuldigung. Sie können nicht direkt auf diese Datei zugreifen."
|
||||
|
||||
msgid "No assets"
|
||||
msgstr "Keine Assets"
|
||||
|
||||
Binary file not shown.
@@ -156,3 +156,57 @@ msgstr "Last backup"
|
||||
|
||||
msgid "Show"
|
||||
msgstr "Show"
|
||||
|
||||
msgid "HTTP"
|
||||
msgstr "HTTP"
|
||||
|
||||
msgid "HTTPS"
|
||||
msgstr "HTTPS"
|
||||
|
||||
msgid "API Error"
|
||||
msgstr "API Error"
|
||||
|
||||
msgid "Unknown"
|
||||
msgstr "Unknown"
|
||||
|
||||
msgid "Asset Definition"
|
||||
msgstr "Asset Definition"
|
||||
|
||||
msgid "Legacy"
|
||||
msgstr "Legacy"
|
||||
|
||||
msgid "UrBackup Servers"
|
||||
msgstr "UrBackup Servers"
|
||||
|
||||
msgid "Invalid parameters"
|
||||
msgstr "Invalid parameters"
|
||||
|
||||
msgid "Item type not enabled for UrBackup"
|
||||
msgstr "Item type not enabled for UrBackup"
|
||||
|
||||
msgid "No server selected"
|
||||
msgstr "No server selected"
|
||||
|
||||
msgid "Asset is already linked to a server"
|
||||
msgstr "Asset is already linked to a server"
|
||||
|
||||
msgid "Failed to link asset to server"
|
||||
msgstr "Failed to link asset to server"
|
||||
|
||||
msgid "Failed to disconnect asset from server"
|
||||
msgstr "Failed to disconnect asset from server"
|
||||
|
||||
msgid "No permission"
|
||||
msgstr "No permission"
|
||||
|
||||
msgid "Invalid server ID"
|
||||
msgstr "Invalid server ID"
|
||||
|
||||
msgid "Server not found"
|
||||
msgstr "Server not found"
|
||||
|
||||
msgid "Sorry. You cannot access this file directly."
|
||||
msgstr "Sorry. You cannot access this file directly."
|
||||
|
||||
msgid "No assets"
|
||||
msgstr "No assets"
|
||||
|
||||
Binary file not shown.
@@ -156,3 +156,57 @@ msgstr "Ultimo backup"
|
||||
|
||||
msgid "Show"
|
||||
msgstr "Mostra"
|
||||
|
||||
msgid "HTTP"
|
||||
msgstr "HTTP"
|
||||
|
||||
msgid "HTTPS"
|
||||
msgstr "HTTPS"
|
||||
|
||||
msgid "API Error"
|
||||
msgstr "Errore API"
|
||||
|
||||
msgid "Unknown"
|
||||
msgstr "Sconosciuto"
|
||||
|
||||
msgid "Asset Definition"
|
||||
msgstr "Definizione asset"
|
||||
|
||||
msgid "Legacy"
|
||||
msgstr "Legacy"
|
||||
|
||||
msgid "UrBackup Servers"
|
||||
msgstr "Server UrBackup"
|
||||
|
||||
msgid "Invalid parameters"
|
||||
msgstr "Parametri non validi"
|
||||
|
||||
msgid "Item type not enabled for UrBackup"
|
||||
msgstr "Tipo oggetto non abilitato per UrBackup"
|
||||
|
||||
msgid "No server selected"
|
||||
msgstr "Nessun server selezionato"
|
||||
|
||||
msgid "Asset is already linked to a server"
|
||||
msgstr "L'asset è già collegato a un server"
|
||||
|
||||
msgid "Failed to link asset to server"
|
||||
msgstr "Collegamento asset al server fallito"
|
||||
|
||||
msgid "Failed to disconnect asset from server"
|
||||
msgstr "Disconnessione asset dal server fallita"
|
||||
|
||||
msgid "No permission"
|
||||
msgstr "Nessun permesso"
|
||||
|
||||
msgid "Invalid server ID"
|
||||
msgstr "ID server non valido"
|
||||
|
||||
msgid "Server not found"
|
||||
msgstr "Server non trovato"
|
||||
|
||||
msgid "Sorry. You cannot access this file directly."
|
||||
msgstr "Spiacenti. Non puoi accedere direttamente a questo file."
|
||||
|
||||
msgid "No assets"
|
||||
msgstr "Nessun Asset"
|
||||
|
||||
@@ -10,3 +10,46 @@
|
||||
color: #b00;
|
||||
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,6 +10,7 @@
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/plugins/urbackup/front/server_test.ajax.php', true);
|
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
xhr.setRequestHeader('X-Glpi-Csrf-Token', getAjaxCsrfToken());
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
xhr.timeout = 8000;
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/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,19 +1,30 @@
|
||||
<?php
|
||||
|
||||
// Force OPcache to reload this file when accessed via web
|
||||
declare(strict_types=1);
|
||||
|
||||
// Force OPcache to reload plugin files when accessed via web
|
||||
if (PHP_SAPI !== 'cli' && function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate(__FILE__, true);
|
||||
$files = new RecursiveIteratorIterator(
|
||||
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 GlpiPlugin\Urbackup\AssetTab;
|
||||
use GlpiPlugin\Urbackup\Capacity\UrBackupCapacity;
|
||||
use GlpiPlugin\Urbackup\Config;
|
||||
use GlpiPlugin\Urbackup\Profile;
|
||||
use GlpiPlugin\Urbackup\Server;
|
||||
use GlpiPlugin\Urbackup\ServerAsset;
|
||||
use GlpiPlugin\Urbackup\MassiveAction as PluginUrbackupMassiveAction;
|
||||
|
||||
define('PLUGIN_URBACKUP_VERSION', '0.4.2');
|
||||
define('PLUGIN_URBACKUP_VERSION', '0.7.0');
|
||||
define('PLUGIN_URBACKUP_MIN_GLPI', '11.0.0');
|
||||
define('PLUGIN_URBACKUP_MAX_GLPI', '11.99.99');
|
||||
|
||||
@@ -50,12 +61,19 @@ function plugin_init_urbackup(): void
|
||||
Plugin::registerClass(ServerAsset::class);
|
||||
Plugin::registerClass(PluginUrbackupMassiveAction::class);
|
||||
|
||||
// Register tab on Computer and other assets
|
||||
$enabled_types = Config::getEnabledItemtypes();
|
||||
if (!empty($enabled_types)) {
|
||||
Plugin::registerClass(AssetTab::class, [
|
||||
'addtabon' => $enabled_types,
|
||||
]);
|
||||
// Register tab on Computer (legacy, always enabled)
|
||||
Plugin::registerClass(AssetTab::class, [
|
||||
'addtabon' => ['Computer'],
|
||||
]);
|
||||
|
||||
// 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';
|
||||
@@ -73,18 +91,6 @@ function plugin_init_urbackup(): void
|
||||
$PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['urbackup'] = [
|
||||
'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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +103,7 @@ function plugin_version_urbackup(): array
|
||||
return [
|
||||
'name' => __('UrBackup', 'urbackup'),
|
||||
'version' => PLUGIN_URBACKUP_VERSION,
|
||||
'author' => 'Finstral',
|
||||
'author' => 'Mariano Benzi',
|
||||
'license' => 'GPL-2.0-or-later',
|
||||
'homepage' => '',
|
||||
'requirements' => [
|
||||
|
||||
+65
-23
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------------
|
||||
* UrBackup plugin for GLPI
|
||||
@@ -55,15 +57,19 @@ class AssetTab extends CommonDBTM
|
||||
*/
|
||||
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0): string
|
||||
{
|
||||
if (!Config::isItemtypeEnabled($item::class)) {
|
||||
$itemtype = $item::class;
|
||||
$enabled = Config::isItemtypeEnabled($itemtype);
|
||||
$hasRight = Profile::canCurrentUser(READ);
|
||||
|
||||
if (!$enabled) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!Profile::canCurrentUser(READ)) {
|
||||
if (!$hasRight) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-upload');
|
||||
return self::createTabEntry(__('UrBackup', 'urbackup'), 0, null, 'ti ti-cloud-up');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,7 +232,7 @@ class AssetTab extends CommonDBTM
|
||||
echo "<td>" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . "</td>";
|
||||
echo "<td>" . htmlspecialchars((string) ($server->fields['server_version'] ?? '')) . "</td>";
|
||||
echo "<td>" . htmlspecialchars(__('Client name', 'urbackup')) . "</td>";
|
||||
echo "<td>" . htmlspecialchars(ServerAsset::getAssetName($item::class, (int) $item->fields['id'])) . "</td>";
|
||||
echo "<td>" . htmlspecialchars((string) ($item->fields['name'] ?? '')) . "</td>";
|
||||
echo "</tr>";
|
||||
|
||||
echo "</table>";
|
||||
@@ -257,9 +263,14 @@ class AssetTab extends CommonDBTM
|
||||
*/
|
||||
private static function loadApiData(CommonDBTM $item, Server $server, array $link): array
|
||||
{
|
||||
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||||
$client_name = (string) ($item->fields['name'] ?? '');
|
||||
$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 = [
|
||||
'client_found' => false,
|
||||
'client_status' => [],
|
||||
@@ -303,6 +314,11 @@ class AssetTab extends CommonDBTM
|
||||
$data['error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
$_SESSION[$cache_key] = [
|
||||
'time' => time(),
|
||||
'data' => $data,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -324,15 +340,42 @@ class AssetTab extends CommonDBTM
|
||||
): void {
|
||||
echo "<div class='plugin-urbackup-inner-tabs'>";
|
||||
|
||||
echo "<h3>" . htmlspecialchars(__('State', 'urbackup')) . "</h3>";
|
||||
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
|
||||
|
||||
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);
|
||||
echo '</div>';
|
||||
|
||||
echo "<h3>" . htmlspecialchars(__('Actions', 'urbackup')) . "</h3>";
|
||||
self::showActionsSection($item, $server, $link, $api_data);
|
||||
if ($canWrite) {
|
||||
echo '<div class="tab-pane fade" id="actions" role="tabpanel">';
|
||||
self::showActionsSection($item, $server, $link, $api_data);
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo "<h3>" . htmlspecialchars(__('Info / Log', 'urbackup')) . "</h3>";
|
||||
echo '<div class="tab-pane fade" id="logs" role="tabpanel">';
|
||||
self::showInfoLogSection($api_data);
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
@@ -427,17 +470,6 @@ class AssetTab extends CommonDBTM
|
||||
echo "<table class='tab_cadre_fixe'>";
|
||||
echo "<tr><th colspan='2'>" . htmlspecialchars(__('Available actions', 'urbackup')) . "</th></tr>";
|
||||
|
||||
if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) {
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td colspan='2'>";
|
||||
echo htmlspecialchars(__('You do not have permission for UrBackup actions.', 'urbackup'));
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
echo "</table>";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$api_data['client_found'] && Profile::canCurrentUser(CREATE)) {
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td>" . htmlspecialchars(__('Create client in UrBackup', 'urbackup')) . "</td>";
|
||||
@@ -465,10 +497,20 @@ class AssetTab extends CommonDBTM
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td>" . htmlspecialchars(__('Backup commands', 'urbackup')) . "</td>";
|
||||
echo "<td>";
|
||||
echo "<div style='display:flex;gap:24px'>";
|
||||
echo "<div>";
|
||||
echo "<strong>" . htmlspecialchars(__('File backup', 'urbackup')) . "</strong><br>";
|
||||
self::showActionButton($item, 'incremental_file_backup', __('Incremental file backup', 'urbackup'), 'btn btn-secondary');
|
||||
echo "<br>";
|
||||
self::showActionButton($item, 'full_file_backup', __('Full file backup', 'urbackup'), 'btn btn-secondary');
|
||||
echo "</div>";
|
||||
echo "<div>";
|
||||
echo "<strong>" . htmlspecialchars(__('Image backup', 'urbackup')) . "</strong><br>";
|
||||
self::showActionButton($item, 'incremental_image_backup', __('Incremental image backup', 'urbackup'), 'btn btn-secondary');
|
||||
echo "<br>";
|
||||
self::showActionButton($item, 'full_image_backup', __('Full image backup', 'urbackup'), 'btn btn-secondary');
|
||||
echo "</div>";
|
||||
echo "</div>";
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
|
||||
@@ -803,7 +845,7 @@ class AssetTab extends CommonDBTM
|
||||
return false;
|
||||
}
|
||||
|
||||
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||||
$client_name = (string) ($item->fields['name'] ?? '');
|
||||
if ($client_name === '') {
|
||||
return false;
|
||||
}
|
||||
@@ -841,7 +883,7 @@ class AssetTab extends CommonDBTM
|
||||
return false;
|
||||
}
|
||||
|
||||
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||||
$client_name = (string) ($item->fields['name'] ?? '');
|
||||
if ($client_name === '') {
|
||||
return false;
|
||||
}
|
||||
@@ -871,7 +913,7 @@ class AssetTab extends CommonDBTM
|
||||
return false;
|
||||
}
|
||||
|
||||
$client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']);
|
||||
$client_name = (string) ($item->fields['name'] ?? '');
|
||||
if ($client_name === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
-282
@@ -11,8 +11,7 @@ declare(strict_types=1);
|
||||
namespace GlpiPlugin\Urbackup;
|
||||
|
||||
use CommonDBTM;
|
||||
use Computer;
|
||||
use Glpi\Asset\AssetDefinition;
|
||||
use Glpi\Asset\Asset;
|
||||
use Glpi\Asset\AssetDefinitionManager;
|
||||
use DBmysql;
|
||||
use Html;
|
||||
@@ -53,67 +52,8 @@ class Config extends CommonDBTM
|
||||
*/
|
||||
public static function ensureDefaultConfiguration(): void
|
||||
{
|
||||
self::ensureAssetType('Computer', true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'),
|
||||
]
|
||||
);
|
||||
// Computer is always enabled by default.
|
||||
// Asset Definition types are managed via the native Capacities UI.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,8 +75,6 @@ class Config extends CommonDBTM
|
||||
*/
|
||||
public static function isItemtypeEnabled(string $itemtype): bool
|
||||
{
|
||||
global $DB;
|
||||
|
||||
if ($itemtype === '') {
|
||||
return false;
|
||||
}
|
||||
@@ -145,22 +83,15 @@ class Config extends CommonDBTM
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = self::getAssetTypesTable();
|
||||
|
||||
if (!$DB->tableExists($table)) {
|
||||
return false;
|
||||
// All Asset subclasses are enabled by default.
|
||||
// The UrBackupCapacity is still registered for cleanup (onCapacityDisabled)
|
||||
// and usage tracking in the Capacities UI, but visibility is always on
|
||||
// so the tab appears out of the box on any Asset Definition.
|
||||
if (is_a($itemtype, Asset::class, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$iterator = $DB->request([
|
||||
'FROM' => $table,
|
||||
'WHERE' => [
|
||||
'itemtype' => $itemtype,
|
||||
'is_active' => 1,
|
||||
],
|
||||
'LIMIT' => 1,
|
||||
]);
|
||||
|
||||
return count($iterator) > 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,54 +101,19 @@ class Config extends CommonDBTM
|
||||
*/
|
||||
public static function getEnabledItemtypes(): array
|
||||
{
|
||||
global $DB;
|
||||
$itemtypes = ['Computer'];
|
||||
|
||||
$types = ['Computer'];
|
||||
$table = self::getAssetTypesTable();
|
||||
|
||||
if (!$DB->tableExists($table)) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
$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;
|
||||
if (class_exists(AssetDefinitionManager::class)) {
|
||||
$manager = AssetDefinitionManager::getInstance();
|
||||
foreach ($manager->getDefinitions() as $definition) {
|
||||
$classname = $definition->getAssetClassName();
|
||||
if (!in_array($classname, $itemtypes, true)) {
|
||||
$itemtypes[] = $classname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
]);
|
||||
}
|
||||
return $itemtypes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,71 +128,22 @@ class Config extends CommonDBTM
|
||||
{
|
||||
Session::checkRight('config', UPDATE);
|
||||
|
||||
echo "<form method='post' action='" . htmlspecialchars($options['target'] ?? '') . "'>";
|
||||
echo "<div class='center'>";
|
||||
echo "<table class='tab_cadre_fixe'>";
|
||||
echo "<tr><th colspan='4'>" . htmlspecialchars(__('UrBackup configuration', 'urbackup')) . "</th></tr>";
|
||||
echo "<tr>";
|
||||
echo "<th>" . htmlspecialchars(__('Asset type', 'urbackup')) . "</th>";
|
||||
echo "<th>" . htmlspecialchars(__('Enabled', 'urbackup')) . "</th>";
|
||||
echo "<th>" . htmlspecialchars(__('Default', 'urbackup')) . "</th>";
|
||||
echo "<th>" . htmlspecialchars(__('Type', 'urbackup')) . "</th>";
|
||||
echo "<tr><th colspan='2'>" . htmlspecialchars(__('UrBackup configuration', 'urbackup')) . "</th></tr>";
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td><strong>" . htmlspecialchars(__('Computer', 'urbackup')) . "</strong></td>";
|
||||
echo "<td><span class='badge bg-success'>" . htmlspecialchars(__('Always enabled', 'urbackup')) . "</span></td>";
|
||||
echo "</tr>";
|
||||
|
||||
foreach (self::getConfigurableAssetTypes() as $itemtype => $label) {
|
||||
$enabled = self::isItemtypeEnabled($itemtype);
|
||||
$is_default = ($itemtype === 'Computer');
|
||||
$is_asset_definition = self::isAssetDefinition($itemtype);
|
||||
|
||||
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>";
|
||||
|
||||
Html::closeForm();
|
||||
echo "<br>";
|
||||
echo "<div class='alert alert-info'>";
|
||||
echo htmlspecialchars(
|
||||
__('For Asset Definition types, enable/disable UrBackup via Config > Asset definitions > Capacities.', 'urbackup')
|
||||
);
|
||||
echo "</div>";
|
||||
echo "</div>";
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -304,6 +151,9 @@ class Config extends CommonDBTM
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return void
|
||||
@@ -311,104 +161,5 @@ class Config extends CommonDBTM
|
||||
public static function saveConfiguration(array $input): void
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
<?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,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------------
|
||||
* UrBackup plugin for GLPI
|
||||
|
||||
+18
-3
@@ -14,7 +14,7 @@ class Profile extends \Profile
|
||||
|
||||
public static function getIcon()
|
||||
{
|
||||
return 'ti ti-server';
|
||||
return 'ti ti-cloud-up';
|
||||
}
|
||||
|
||||
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
|
||||
@@ -73,7 +73,22 @@ class Profile extends \Profile
|
||||
$profiles_id = (int) ($_SESSION['glpiactiveprofile']['id'] ?? 0);
|
||||
|
||||
if ($profiles_id > 0) {
|
||||
self::setProfileRights($profiles_id, READ | UPDATE | CREATE | DELETE);
|
||||
self::setProfileRights($profiles_id, READ | UPDATE | CREATE | DELETE | PURGE);
|
||||
} 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;
|
||||
@@ -83,7 +98,7 @@ class Profile extends \Profile
|
||||
]);
|
||||
|
||||
foreach ($all_profiles as $profile) {
|
||||
if ($profile['id'] !== $profiles_id) {
|
||||
if ((int) $profile['id'] !== $profiles_id) {
|
||||
$existing = $DB->request([
|
||||
'FROM' => 'glpi_profilerights',
|
||||
'WHERE' => [
|
||||
|
||||
+582
-52
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------------
|
||||
* UrBackup plugin for GLPI
|
||||
@@ -12,9 +14,12 @@ use CommonDBTM;
|
||||
use CommonGLPI;
|
||||
use Dropdown;
|
||||
use Entity;
|
||||
use Group;
|
||||
use Html;
|
||||
use Location;
|
||||
use Session;
|
||||
use State;
|
||||
use User;
|
||||
|
||||
class Server extends CommonDBTM
|
||||
{
|
||||
@@ -151,7 +156,7 @@ class Server extends CommonDBTM
|
||||
if (self::canView()) {
|
||||
$menu['title'] = self::getMenuName();
|
||||
$menu['page'] = self::getSearchURL(false);
|
||||
$menu['icon'] = 'ti ti-server';
|
||||
$menu['icon'] = 'ti ti-cloud-up';
|
||||
|
||||
$menu['links']['search'] = self::getSearchURL(false);
|
||||
|
||||
@@ -254,6 +259,14 @@ class Server extends CommonDBTM
|
||||
|
||||
$tab[] = [
|
||||
'id' => 8,
|
||||
'table' => User::getTable(),
|
||||
'field' => 'name',
|
||||
'name' => User::getTypeName(1),
|
||||
'datatype' => 'dropdown',
|
||||
];
|
||||
|
||||
$tab[] = [
|
||||
'id' => 9,
|
||||
'table' => self::getTable(),
|
||||
'field' => 'is_active',
|
||||
'name' => __('Active'),
|
||||
@@ -261,7 +274,7 @@ class Server extends CommonDBTM
|
||||
];
|
||||
|
||||
$tab[] = [
|
||||
'id' => 9,
|
||||
'id' => 10,
|
||||
'table' => self::getTable(),
|
||||
'field' => 'last_api_status',
|
||||
'name' => __('Last API status', 'urbackup'),
|
||||
@@ -269,7 +282,7 @@ class Server extends CommonDBTM
|
||||
];
|
||||
|
||||
$tab[] = [
|
||||
'id' => 10,
|
||||
'id' => 11,
|
||||
'table' => self::getTable(),
|
||||
'field' => 'last_api_check',
|
||||
'name' => __('Last API check', 'urbackup'),
|
||||
@@ -277,7 +290,7 @@ class Server extends CommonDBTM
|
||||
];
|
||||
|
||||
$tab[] = [
|
||||
'id' => 11,
|
||||
'id' => 12,
|
||||
'table' => self::getTable(),
|
||||
'field' => 'date_creation',
|
||||
'name' => __('Creation date'),
|
||||
@@ -285,22 +298,24 @@ class Server extends CommonDBTM
|
||||
];
|
||||
|
||||
$tab[] = [
|
||||
'id' => 12,
|
||||
'id' => 13,
|
||||
'table' => self::getTable(),
|
||||
'field' => 'date_mod',
|
||||
'name' => __('Last update'),
|
||||
'datatype' => 'datetime',
|
||||
];
|
||||
|
||||
$tab[] = [
|
||||
'id' => 13,
|
||||
'table' => self::getTable(),
|
||||
'field' => 'id',
|
||||
'name' => __('View', 'urbackup'),
|
||||
'massiveaction' => false,
|
||||
'datatype' => 'raw',
|
||||
'searchtype' => 'view',
|
||||
];
|
||||
if (Session::haveRight(self::$rightname, UPDATE)) {
|
||||
$tab[] = [
|
||||
'id' => 14,
|
||||
'table' => self::getTable(),
|
||||
'field' => 'id',
|
||||
'name' => __('View', 'urbackup'),
|
||||
'massiveaction' => false,
|
||||
'datatype' => 'raw',
|
||||
'searchtype' => 'view',
|
||||
];
|
||||
}
|
||||
|
||||
return $tab;
|
||||
}
|
||||
@@ -324,7 +339,14 @@ class Server extends CommonDBTM
|
||||
|
||||
$this->initForm($ID, $options);
|
||||
$this->showFormHeader($options);
|
||||
$this->showFormFields($ID > 0 ? (int) $ID : null);
|
||||
$this->showFormButtons($options);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function showFormFields(?int $ID): void
|
||||
{
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td>" . htmlspecialchars(__('Name')) . "</td>";
|
||||
echo "<td>";
|
||||
@@ -336,7 +358,10 @@ class Server extends CommonDBTM
|
||||
|
||||
echo "<td>" . htmlspecialchars(__('Active')) . "</td>";
|
||||
echo "<td>";
|
||||
Dropdown::showYesNo('is_active', (int) ($this->fields['is_active'] ?? 1));
|
||||
Dropdown::showYesNo(
|
||||
'is_active',
|
||||
($ID !== null && $ID > 0) ? (int) ($this->fields['is_active'] ?? 1) : 1
|
||||
);
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
|
||||
@@ -374,8 +399,8 @@ class Server extends CommonDBTM
|
||||
Dropdown::showFromArray(
|
||||
'protocol',
|
||||
[
|
||||
'http' => 'HTTP',
|
||||
'https' => 'HTTPS',
|
||||
'http' => __('HTTP', 'urbackup'),
|
||||
'https' => __('HTTPS', 'urbackup'),
|
||||
],
|
||||
[
|
||||
'value' => $this->fields['protocol'] ?? 'http',
|
||||
@@ -419,21 +444,33 @@ class Server extends CommonDBTM
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
|
||||
$canEdit = $ID > 0
|
||||
? Session::haveRight(self::$rightname, UPDATE)
|
||||
: Session::haveRight(self::$rightname, CREATE);
|
||||
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td>" . htmlspecialchars(__('API username', 'urbackup')) . "</td>";
|
||||
echo "<td>";
|
||||
echo Html::input('api_username', [
|
||||
'value' => $this->fields['api_username'] ?? '',
|
||||
'size' => 40,
|
||||
'autocomplete' => 'off',
|
||||
]);
|
||||
if ($canEdit) {
|
||||
echo Html::input('api_username', [
|
||||
'value' => $this->fields['api_username'] ?? '',
|
||||
'size' => 40,
|
||||
'autocomplete' => 'off',
|
||||
]);
|
||||
} else {
|
||||
echo htmlspecialchars($this->fields['api_username'] ?? '');
|
||||
}
|
||||
echo "</td>";
|
||||
|
||||
echo "<td>" . htmlspecialchars(__('API password', 'urbackup')) . "</td>";
|
||||
echo "<td>";
|
||||
echo "<input type='password' name='api_password' value='" .
|
||||
htmlspecialchars((string) ($this->fields['api_password'] ?? '')) .
|
||||
"' autocomplete='new-password'>";
|
||||
if ($canEdit) {
|
||||
echo "<input type='password' name='api_password' value='" .
|
||||
htmlspecialchars((string) ($this->fields['api_password'] ?? '')) .
|
||||
"' autocomplete='new-password'>";
|
||||
} else {
|
||||
echo '******';
|
||||
}
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
|
||||
@@ -464,7 +501,7 @@ class Server extends CommonDBTM
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
|
||||
$apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
$apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td>" . htmlspecialchars(__('API connection status', 'urbackup')) . "<br><small class='text-muted'>" . htmlspecialchars(__('Click Save to test connection', 'urbackup')) . "</small></td>";
|
||||
echo "<td colspan='3'>";
|
||||
@@ -479,10 +516,6 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
$this->showFormButtons($options);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -542,6 +575,30 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -551,6 +608,16 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
return $input;
|
||||
}
|
||||
|
||||
if (isset($input['port']) && ($input['port'] === '' || $input['port'] === null)) {
|
||||
$input['port'] = 55414;
|
||||
}
|
||||
if (isset($input['protocol']) && $input['protocol'] === '') {
|
||||
$input['protocol'] = 'http';
|
||||
}
|
||||
if (isset($input['ignore_ssl']) && ($input['ignore_ssl'] === '' || $input['ignore_ssl'] === null)) {
|
||||
$input['ignore_ssl'] = 0;
|
||||
}
|
||||
|
||||
if (!empty($input['id']) && (int) $input['id'] > 0) {
|
||||
$server = new self();
|
||||
if ($server->getFromDB((int) $input['id'])) {
|
||||
@@ -780,7 +847,7 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo '<div class="alert alert-danger">';
|
||||
echo 'API Error: ' . htmlspecialchars($e->getMessage());
|
||||
echo htmlspecialchars(__('API Error', 'urbackup')) . ': ' . htmlspecialchars($e->getMessage());
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
@@ -789,7 +856,6 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
echo '<thead>';
|
||||
echo '<tr>';
|
||||
echo '<th>' . htmlspecialchars(__('Asset', 'urbackup')) . '</th>';
|
||||
echo '<th>' . htmlspecialchars(__('Name', 'urbackup')) . '</th>';
|
||||
echo '<th>' . htmlspecialchars(__('Client name', 'urbackup')) . '</th>';
|
||||
echo '<th>' . htmlspecialchars(__('Version', 'urbackup')) . '</th>';
|
||||
echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>';
|
||||
@@ -840,8 +906,14 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
$itemUrl = $glpiItem ? $glpiItem->getLinkURL() : '';
|
||||
|
||||
echo '<tr>';
|
||||
echo '<td>' . htmlspecialchars($itemTypeLabel . ' #' . $link['items_id']) . '</td>';
|
||||
echo '<td>' . ($itemUrl ? '<a href="' . htmlspecialchars($itemUrl) . '">' . htmlspecialchars($clientName) . '</a>' : htmlspecialchars($clientName)) . '</td>';
|
||||
echo '<td>';
|
||||
if ($itemUrl) {
|
||||
echo '<a href="' . htmlspecialchars($itemUrl) . '">' . htmlspecialchars($clientName) . '</a>';
|
||||
} else {
|
||||
echo htmlspecialchars($clientName);
|
||||
}
|
||||
echo ' <small class="text-muted">(' . htmlspecialchars($itemTypeLabel) . ')</small>';
|
||||
echo '</td>';
|
||||
echo '<td>' . htmlspecialchars($urbackupClientName) . '</td>';
|
||||
echo '<td>' . htmlspecialchars($clientVersion) . '</td>';
|
||||
echo '<td>' . $statusHtml . '</td>';
|
||||
@@ -949,6 +1021,8 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
|
||||
|
||||
echo '<table class="table table-striped table-hover">';
|
||||
echo '<thead>';
|
||||
echo '<tr>';
|
||||
@@ -957,13 +1031,15 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
echo '<th>' . htmlspecialchars(__('Status', 'urbackup')) . '</th>';
|
||||
echo '<th>' . htmlspecialchars(__('Last backup', 'urbackup')) . '</th>';
|
||||
echo '<th>' . htmlspecialchars(__('IP address', 'urbackup')) . '</th>';
|
||||
echo '<th>' . htmlspecialchars(__('Actions', 'urbackup')) . '</th>';
|
||||
if ($canWrite) {
|
||||
echo '<th>' . htmlspecialchars(__('Actions', 'urbackup')) . '</th>';
|
||||
}
|
||||
echo '</tr>';
|
||||
echo '</thead>';
|
||||
echo '<tbody>';
|
||||
|
||||
foreach ($unlinkedClients as $uc) {
|
||||
$clientName = (string) ($uc['name'] ?? 'Unknown');
|
||||
$clientName = (string) ($uc['name'] ?? __('Unknown', 'urbackup'));
|
||||
$clientNameLower = strtolower($clientName);
|
||||
|
||||
$online = $uc['online'] ?? null;
|
||||
@@ -982,27 +1058,481 @@ $apiStatus = (int) ($this->fields['last_api_status'] ?? 0);
|
||||
echo '<td>' . $statusHtml . '</td>';
|
||||
echo '<td>' . htmlspecialchars($lastBackup ?: '-') . '</td>';
|
||||
echo '<td>' . htmlspecialchars($clientIp ?: '-') . '</td>';
|
||||
echo '<td>';
|
||||
if (isset($linkableAssets[$clientNameLower])) {
|
||||
$match = $linkableAssets[$clientNameLower];
|
||||
$formAction = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php';
|
||||
echo '<form method="post" action="' . htmlspecialchars($formAction) . '" class="d-inline">';
|
||||
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
|
||||
echo Html::hidden('itemtype', ['value' => $match['itemtype']]);
|
||||
echo Html::hidden('items_id', ['value' => $match['items_id']]);
|
||||
echo Html::hidden('id', ['value' => (int) $server->fields['id']]);
|
||||
echo '<button type="submit" name="link_asset" value="1" class="btn btn-primary btn-sm">';
|
||||
echo htmlspecialchars(__('Connect'));
|
||||
echo '</button>';
|
||||
Html::closeForm();
|
||||
} else {
|
||||
echo '<span class="text-muted">—</span>';
|
||||
if ($canWrite) {
|
||||
echo '<td>';
|
||||
if (isset($linkableAssets[$clientNameLower])) {
|
||||
$match = $linkableAssets[$clientNameLower];
|
||||
$formAction = PLUGIN_URBACKUP_WEB_DIR . '/front/server.form.php';
|
||||
echo '<form method="post" action="' . htmlspecialchars($formAction) . '" class="d-inline">';
|
||||
echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
|
||||
echo Html::hidden('itemtype', ['value' => $match['itemtype']]);
|
||||
echo Html::hidden('items_id', ['value' => $match['items_id']]);
|
||||
echo Html::hidden('id', ['value' => (int) $server->fields['id']]);
|
||||
echo '<button type="submit" name="link_asset" value="1" class="btn btn-primary btn-sm">';
|
||||
echo htmlspecialchars(__('Connect'));
|
||||
echo '</button>';
|
||||
Html::closeForm();
|
||||
} else {
|
||||
echo '<span class="text-muted">—</span>';
|
||||
}
|
||||
echo '</td>';
|
||||
}
|
||||
echo '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
echo '</tbody>';
|
||||
echo '</table>';
|
||||
}
|
||||
|
||||
public static function showMissingClientsTab(Server $server): void
|
||||
{
|
||||
global $DB;
|
||||
|
||||
$canWrite = Session::haveRight(self::$rightname, UPDATE) || Session::haveRight(self::$rightname, CREATE);
|
||||
|
||||
$apiStatus = (int) ($server->fields['last_api_status'] ?? 0);
|
||||
if ($apiStatus !== 1) {
|
||||
echo '<div class="alert alert-warning">';
|
||||
echo htmlspecialchars(__('API connection not working. Save server to test connection.', 'urbackup'));
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$serverLocationId = (int) ($server->fields['locations_id'] ?? 0);
|
||||
if ($serverLocationId <= 0) {
|
||||
echo '<div class="alert alert-info">';
|
||||
echo htmlspecialchars(__('No location configured for this server.', 'urbackup'));
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$rootLocationId = LocationHelper::getRootLocationId($serverLocationId);
|
||||
|
||||
$linkedIterator = $DB->request([
|
||||
'FROM' => ServerAsset::getTable(),
|
||||
]);
|
||||
$linkedAssetKeys = [];
|
||||
foreach ($linkedIterator as $row) {
|
||||
$linkedAssetKeys[$row['itemtype'] . ':' . $row['items_id']] = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$client = new UrbackupApiClient($server);
|
||||
$urbackupClients = $client->getStatus();
|
||||
} catch (\Throwable $e) {
|
||||
echo '<div class="alert alert-danger">';
|
||||
echo htmlspecialchars($e->getMessage());
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$urbackupClientNames = [];
|
||||
foreach ($urbackupClients as $uc) {
|
||||
$name = strtolower((string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? ''));
|
||||
if ($name !== '') {
|
||||
$urbackupClientNames[$name] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$cacheEntity = [];
|
||||
$cacheLocation = [];
|
||||
$cacheState = [];
|
||||
$cacheUser = [];
|
||||
$cacheGroup = [];
|
||||
|
||||
$itemtypes = Config::getEnabledItemtypes();
|
||||
$missingAssets = [];
|
||||
$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 '';
|
||||
}
|
||||
}
|
||||
+6
-14
@@ -79,7 +79,6 @@ class ServerAsset extends CommonDBTM
|
||||
}
|
||||
|
||||
$table = self::getTable();
|
||||
$date = $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s');
|
||||
|
||||
$existing = self::getLinkForAsset($itemtype, $items_id, false);
|
||||
|
||||
@@ -88,10 +87,6 @@ class ServerAsset extends CommonDBTM
|
||||
$table,
|
||||
[
|
||||
'plugin_urbackup_servers_id' => $server_id,
|
||||
'client_name' => (string) ($item->fields['name'] ?? ''),
|
||||
'client_ip' => self::extractAssetIp($item),
|
||||
'is_active' => 1,
|
||||
'date_mod' => $date,
|
||||
],
|
||||
[
|
||||
'id' => (int) $existing['id'],
|
||||
@@ -243,43 +238,40 @@ class ServerAsset extends CommonDBTM
|
||||
|
||||
echo "<div class='center'>";
|
||||
echo "<table class='tab_cadre_fixehov'>";
|
||||
echo "<tr><th colspan='5'>" . htmlspecialchars(__('Linked assets', 'urbackup')) . "</th></tr>";
|
||||
echo "<tr><th colspan='4'>" . htmlspecialchars(__('Linked assets', 'urbackup')) . "</th></tr>";
|
||||
echo "<tr>";
|
||||
echo "<th>" . htmlspecialchars(__('Asset', 'urbackup')) . "</th>";
|
||||
echo "<th>" . htmlspecialchars(__('Type')) . "</th>";
|
||||
echo "<th>" . htmlspecialchars(__('IP address', 'urbackup')) . "</th>";
|
||||
echo "<th>" . htmlspecialchars(__('Last file backup', 'urbackup')) . "</th>";
|
||||
echo "<th>" . htmlspecialchars(__('Last image backup', 'urbackup')) . "</th>";
|
||||
echo "</tr>";
|
||||
|
||||
$iterator = $DB->request([
|
||||
'FROM' => self::getTable(),
|
||||
'WHERE' => [
|
||||
'plugin_urbackup_servers_id' => (int) $item->fields['id'],
|
||||
'is_active' => 1,
|
||||
],
|
||||
'ORDER' => 'client_name',
|
||||
]);
|
||||
|
||||
foreach ($iterator as $row) {
|
||||
$asset_label = (string) $row['client_name'];
|
||||
$itemtype = (string) $row['itemtype'];
|
||||
$items_id = (int) $row['items_id'];
|
||||
|
||||
$asset_label = (string) $items_id;
|
||||
$asset_ip = '';
|
||||
|
||||
if (class_exists($itemtype)) {
|
||||
$asset = new $itemtype();
|
||||
|
||||
if ($asset instanceof CommonDBTM && $asset->getFromDB($items_id)) {
|
||||
$asset_label = $asset->getLink();
|
||||
$asset_ip = Server::getAssetIp($itemtype, $items_id);
|
||||
}
|
||||
}
|
||||
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td>" . $asset_label . "</td>";
|
||||
echo "<td>" . htmlspecialchars($itemtype) . "</td>";
|
||||
echo "<td>" . htmlspecialchars((string) $row['client_ip']) . "</td>";
|
||||
echo "<td>" . htmlspecialchars((string) $row['last_file_backup']) . "</td>";
|
||||
echo "<td>" . htmlspecialchars((string) $row['last_image_backup']) . "</td>";
|
||||
echo "<td>" . htmlspecialchars($asset_ip ?: '-') . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ class UrbackupApiClient
|
||||
|
||||
private bool $ignore_ssl;
|
||||
|
||||
private int $timeout = 10;
|
||||
private int $timeout = 30;
|
||||
|
||||
private int $connect_timeout = 5;
|
||||
|
||||
private string $session = '';
|
||||
|
||||
@@ -35,6 +37,10 @@ class UrbackupApiClient
|
||||
|
||||
private int $lastlogid = 0;
|
||||
|
||||
private ?array $cached_status = null;
|
||||
|
||||
private array $cached_settings = [];
|
||||
|
||||
private string $server_version = '';
|
||||
|
||||
private bool $is_version_2_4_or_higher = false;
|
||||
@@ -245,21 +251,29 @@ class UrbackupApiClient
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
if ($this->cached_status !== null) {
|
||||
return $this->cached_status;
|
||||
}
|
||||
|
||||
$data = $this->apiAction('status');
|
||||
|
||||
if (isset($data['status']) && is_array($data['status'])) {
|
||||
return array_values($data['status']);
|
||||
$this->cached_status = array_values($data['status']);
|
||||
return $this->cached_status;
|
||||
}
|
||||
|
||||
if (isset($data['clients']) && is_array($data['clients'])) {
|
||||
return array_values($data['clients']);
|
||||
$this->cached_status = array_values($data['clients']);
|
||||
return $this->cached_status;
|
||||
}
|
||||
|
||||
if (array_is_list($data)) {
|
||||
return $data;
|
||||
$this->cached_status = $data;
|
||||
return $this->cached_status;
|
||||
}
|
||||
|
||||
return [];
|
||||
$this->cached_status = [];
|
||||
return $this->cached_status;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,16 +329,22 @@ class UrbackupApiClient
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isset($this->cached_settings[$client_id])) {
|
||||
return $this->cached_settings[$client_id];
|
||||
}
|
||||
|
||||
$data = $this->apiAction('settings', [
|
||||
'sa' => 'clientsettings',
|
||||
't_clientid' => $client_id,
|
||||
]);
|
||||
|
||||
if (isset($data['settings']) && is_array($data['settings'])) {
|
||||
return $data['settings'];
|
||||
$this->cached_settings[$client_id] = $data['settings'];
|
||||
return $this->cached_settings[$client_id];
|
||||
}
|
||||
|
||||
return $data;
|
||||
$this->cached_settings[$client_id] = $data;
|
||||
return $this->cached_settings[$client_id];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,6 +356,11 @@ class UrbackupApiClient
|
||||
*
|
||||
* @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
|
||||
{
|
||||
$client_id = $this->getClientIdByName($client_name);
|
||||
@@ -351,6 +376,7 @@ class UrbackupApiClient
|
||||
$key => (string) $value,
|
||||
]);
|
||||
|
||||
$this->clearSettingsCache($client_id);
|
||||
return $this->responseIsSuccess($data);
|
||||
}
|
||||
|
||||
@@ -369,6 +395,7 @@ class UrbackupApiClient
|
||||
$key => $value,
|
||||
]);
|
||||
|
||||
$this->clearSettingsCache($client_id);
|
||||
return $this->responseIsSuccess($data);
|
||||
}
|
||||
|
||||
@@ -391,6 +418,7 @@ class UrbackupApiClient
|
||||
|
||||
$data = $this->apiAction('settings', $params);
|
||||
|
||||
$this->clearSettingsCache($client_id);
|
||||
return $this->responseIsSuccess($data);
|
||||
}
|
||||
|
||||
@@ -667,6 +695,7 @@ class UrbackupApiClient
|
||||
$options = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => $this->timeout,
|
||||
CURLOPT_CONNECTTIMEOUT => $this->connect_timeout,
|
||||
CURLOPT_SSL_VERIFYPEER => !$this->ignore_ssl,
|
||||
CURLOPT_SSL_VERIFYHOST => $this->ignore_ssl ? 0 : 2,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<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>
|
||||
@@ -1,39 +0,0 @@
|
||||
<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>
|
||||
@@ -1,118 +0,0 @@
|
||||
{% 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 %}
|
||||
@@ -1,51 +0,0 @@
|
||||
{% 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