diff --git a/MEMORY.md b/MEMORY.md index b686ebb..f9313cd 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -1,179 +1,160 @@ # UrBackup Plugin - Memoria di Sviluppo -## 📋 Informazioni Plugin - +## Informazioni Plugin - **Nome**: UrBackup -- **Versione**: 0.4.2 +- **Versione**: 0.5.0 - **Compatibilità**: GLPI 11.0.6+, PHP 8.3+ - **Namespace**: `GlpiPlugin\Urbackup` -## ✅ Funzionalità Implementate +## Struttura File +``` +plugin_urbackup/ +├── setup.php # Hook init, menu, tab, versione, prerequisiti +├── hook.php # Install/uninstall, massive actions, class list +├── composer.json # PSR-4 autoloading +├── src/ +│ ├── Config.php # Configurazione plugin (Computer sempre attivo, capacità) +│ ├── Profile.php # Diritti utente +│ ├── Server.php # CRUD server UrBackup +│ ├── ServerAsset.php # Linking asset-server +│ ├── AssetTab.php # Tab UrBackup su asset GLPI +│ ├── MassiveAction.php # Azioni massive connect/disconnect +│ ├── UrbackupApiClient.php # Client API UrBackup +│ ├── LocationHelper.php # Helper assegnazione server per location +│ ├── Capacity/ +│ │ └── UrBackupCapacity.php # Capacity per Asset Definition GLPI 11 +│ ├── Controller/ +│ │ ├── ServerController.php +│ │ ├── AssetController.php +│ │ └── ConfigController.php +│ └── Command/ +│ └── TestApiCommand.php +├── install/ +│ ├── install.php # Schema DB, migrazioni, capacity migration +│ ├── uninstall.php # Drop tabelle, pulizia diritti +│ └── mysql/ +│ └── plugin_urbackup-empty.sql # Schema iniziale +├── front/ +│ ├── config.form.php # Config plugin (Computer info) +│ ├── server.php # Lista server +│ ├── server.form.php # Form server con tab laterali +│ └── asset.form.php # Azioni asset (connect/disconnect/backup) +├── ajax/ +│ └── server_test.php # Test connessione API +├── templates/ +│ └── config/ +│ └── config.html.twig # Config page Twig +├── locales/ # Traduzioni (it, en, de) +├── css/ & js/ # Asset frontend +└── MEMORY.md # Questo file +``` -### 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` - -### 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`) - -### Classi Principali +## Classi Principali | Classe | Descrizione | |--------|-------------| -| `Config` | Configurazione plugin, tipi asset abilitati | +| `Config` | Configurazione plugin, isItemtypeEnabled | | `Profile` | Diritti utente, permessi | -| `Server` | Server UrBackup (CRUD) | +| `Server` | CRUD server UrBackup | | `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 | +| `AssetTab` | Tab UrBackup su asset | +| `MassiveAction` | Azioni massive connect/disconnect | +| `UrbackupApiClient` | Client API UrBackup (PBKDF2 auth) | +| `LocationHelper` | Assegnazione server per location | +| `Capacity/UrBackupCapacity` | Capacity per Asset Definition (cleanup + tracking) | +| `Controller/ServerController` | Route Symfony per server | +| `Controller/AssetController` | Route Symfony per azioni asset | +| `Command/TestApiCommand` | CLI test API | -### 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 +## Interfacce GLPI +- Menu: Amministrazione → Server UrBackup +- Tab UrBackup su Computer (sempre) + tutti gli Asset Definition +- Config: Computer sempre attivo; Asset Definition gestiti via native Capacities UI -### Interfacce GLPI -- ✅ Menu: Amministrazione → Server UrBackup -- ✅ Tab: UrBackup su Computer e asset abilitati -- ✅ Configurazione: Abilitazione tipi asset (legacy + Asset Definition GLPI 11) +## Architettura Capacities (v0.5.0) -## 🔧 Fix e Correzioni Applicate +### Flusso +``` +Kernel boot: PostBootEvent +├── (110) InitializePlugins → plugin_init_urbackup() +│ ├── Plugin::registerClass(AssetTab, ['Computer']) ← Computer legacy +│ └── AssetDefinitionManager::registerCapacity() ← UrBackupCapacity registrata +│ +├── (100) CustomObjectsBoot → bootDefinitions() +│ └── foreach definition con UrBackupCapacity abilitata: +│ └── UrBackupCapacity::onClassBootstrap($classname) +│ └── CommonGLPI::registerStandardTab($classname, AssetTab::class) +│ ↑ Tab registrato QUI, dopo che le definizioni sono caricate dal DB +│ +Config::isItemtypeEnabled($itemtype) +├── '' → false +├── 'Computer' → true +├── Asset subclass → true ← SEMPRE visibile +└── altro → false +│ +UrBackupCapacity +├── onClassBootstrap() → registra AssetTab sul definition classname +├── onCapacityDisabled() → pulisce ServerAsset entries +├── isUsed() / getCapacityUsageDescription() → UI Capacities +``` -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. +### Separazione responsabilità +- **Registrazione tab**: `UrBackupCapacity::onClassBootstrap()` — chiamata da `bootDefinitions()` per ogni definizione con capacità abilitata +- **Visibilità tab**: `Config::isItemtypeEnabled()` — sempre true per Asset subclasses +- **Pulizia**: `UrBackupCapacity::onCapacityDisabled()` — quando admin disabilita la capacità +- **Config**: Computer sempre attivo; Capacities gestite dalla UI nativa GLPI -### Modifica Test API Connection (v0.4.3) +### Bug risolto: tab non registrato su Asset Definition nuovi +- **Causa**: `setup.php` iterava `getDefinitions()` durante `plugin_init_urbackup()`, ma `bootDefinitions()` (che carica le definizioni dal DB) gira DOPO (priority 100 vs 110), quindi `getDefinitions()` tornava sempre array vuoto +- **Fix**: La registrazione del tab è stata spostata in `UrBackupCapacity::onClassBootstrap()`, chiamata da `bootstrapDefinition()` durante `bootDefinitions()`, quando le definizioni sono già caricate e la classe dinamica è disponibile +- **Rimosso**: loop `foreach ($defs ...)` e debug logging da `setup.php` +- **Rimosso**: debug logging da `AssetTab.php` -**Data**: 2026-05-19 +## Tabella glpi_plugin_urbackup_assettypes +- Colonna `is_default` rimossa (non serve più con capacities) +- Tabella vestigiale — non più usata per la logica applicativa +- Migrazione: `plugin_urbackup_install_convert_assettypes_to_capacities()` abilita capacità su tutte le definizioni -**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 +## Comandi Utili +```bash +# Installare il plugin +php bin/console glpi:plugin:install urbackup -**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) +# Attivare +php bin/console glpi:plugin:activate urbackup -**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 +# Disinstallare +echo "yes" | php bin/console glpi:plugin:uninstall urbackup -### 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 +# Verificare syntax PHP +php -l plugins/urbackup/src/*.php plugins/urbackup/front/*.php plugins/urbackup/*.php +``` +## Checklist Pre-Consegna - [x] `declare(strict_types=1);` in ogni file PHP -- [x] Namespace `Plugin\\` e PSR-4 corretto +- [x] Namespace `GlpiPlugin\Urbackup\` 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 +## Da Completare +1. Test funzionalità lista server e form +2. AJAX endpoint comunicazione API -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 +### Verifiche Post-Migrazione Capacity +- [x] Tab UrBackup appare su Asset Definition nuovo (Computer + Asset) +- [ ] Upgrade da v0.4.x converte righe attive +- [ ] Colonna `is_default` droppata +- [ ] Disabilitazione capacità pulisce ServerAsset (onCapacityDisabled) -## 📌 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 -``` - -## 📚 Riferimenti - -- 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 - ---- - -## 🔌 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 +## API UrBackup - Riferimenti +### Endpoint API | Metodo | Descrizione | |--------|-------------| -| `login` | Autenticazione con username/password | +| `login` | Autenticazione username/password | | `get_status` | Lista client con stato backup | | `get_backups` | Lista backup per client | | `start_backup` | Avvia backup (file/image) | @@ -181,142 +162,57 @@ L'API di UrBackup Server è accessibile via HTTP alla porta 55414 (default). Gli | `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: API restituisce HTML invece di JSON con credenziali errate +- Timeout: Verificare raggiungibilità server UrBackup +- SSL: Se `ignore_ssl` attivo, accettare certificati self-signed +- AJAX 403: Funziona solo da browser con sessione GLPI attiva -- **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 +## Cronologia Modifiche -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. +### v0.4.2 — Refactoring server.form.php e tab laterali +- Side tabs a sinistra con nav-pills +- 4 tab: Server, Linked, Unlinked, Missing clients +- Missing clients con search/sort, location/AIP/group/state +- Navigazione server sopra i tab, indipendente +- Breadcrumb admin +- Fix namespace `getCachedName()`, Gruppo da `glpi_groups_items` +- Permessi: READ per accesso form, Connect/API nascosti per READ +- i18n: 17 stringhe nuove (it, en, de) +- Bug fix: ServerAsset colonne, asset.form.php disconnectAsset(), strict_types in 11 file -**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. +### v0.4.6 — Funzionalità API completate +- Salvataggio Internet Mode su server UrBackup +- Salvataggio Default Dirs +- Backup file (incremental/full) +- Backup immagine (incremental/full) +- Recupero version client da API -**File chiave**: -- `front/server_test.ajax.php` - endpoint AJAX per test API -- `public/js/urbackup.js` - JavaScript per gestire il click del pulsante +### v0.4.5 — Tabella semplificata serverassets +- Rimossi campi: client_name, client_ip, client_version, is_active, last_file_backup, last_image_backup, last_sync, date_creation, date_mod, urbackup_client_id +- Solo: id, plugin_urbackup_servers_id, itemtype, items_id -**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 +### v0.4.4 — Tab Linked/Unlinked Clients +- Dati da API UrBackup invece che da DB +- Nome tabella corretto: `glpi_plugin_urbackup_serverassets` +- Campo: `plugin_urbackup_servers_id` -### Modifica Tab Linked/Unlinked Clients (v0.4.4) +### v0.4.3 — Test API Connection automatico +- Rimosso pulsante manuale +- Rimosse righe last status/message/check +- Stati: verde OK, rosso fallito, giallo irraggiungibile -**Data**: 2026-05-19 +### v0.5.0 — Capacity System per Asset Definition GLPI 11 +- Rimpiazzata tabella custom `glpi_plugin_urbackup_assettypes` con Capacities native +- Computer: legacy (sempre attivo, fuori capacities) +- Asset Definition: tab sempre registrato, capacità gestisce cleanup +- `isItemtypeEnabled()` = true per tutti gli Asset +- `onClassBootstrap()` rimosso (poi reintrodotto in v0.5.1) +- File: `src/Capacity/UrBackupCapacity.php` +- Migration: auto-enable capacità su tutte le definizioni + drop is_default -**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 - -### Refactoring server.form.php e nuove tab (v0.4.7) - -**Data**: 2026-05-21 - -**Descrizione**: Restrutturazione completa della pagina server con tab laterali e nuove funzionalità: - -**Refactoring Server::showForm()**: -- Estratto `showFormFields(?int $ID)` da `showForm()` per permettere layout custom -- `showForm()` ora chiama header + fields + buttons in sequenza - -**Tab laterali (server.form.php)**: -- Side tabs a sinistra (col-2) con `nav-pills flex-column` -- 4 tab: Server, Linked clients, Unlinked clients, Missing clients -- Tab persistence via URL hash (JS su `shown.bs.tab`) - -**Missing clients tab**: -- Mostra asset GLPI con root location matching, non ancora collegati a ServerAsset e non presenti su UrBackup -- Colonne: Name (link all'asset), Entity, Location (completename), Inventory number, IP, State, User, Group -- Ricerca testuale client-side -- Ordinamento colonne click-to-sort vanilla JS -- Navigazione server spostata in server.form.php (sopra i tab, indipendente dalle sezioni) - -**Bug fix namespace risolto**: -- `getCachedName()` usava `new $classname()` con stringa → PHP cercava `GlpiPlugin\Urbackup\Group` invece di `\Group` -- Fix: `$fqcn = '\\' . ltrim($classname, '\\')` per forzare global namespace - -**Gruppo asset corretto**: -- Gruppo non in `glpi_computers.groups_id` (colonna inesistente) -- Letto da `glpi_groups_items` WHERE `type = 1` (GROUP_TYPE_NORMAL) -- Usa `completename` per gerarchia (es. "Helpdesk > Livello 1") - -**Indirizzo IP asset**: -- Query JOIN: `glpi_ipaddresses → glpi_networknames → glpi_networkports` -- Primo IP valido per l'item - -**Breadcrumb**: -- Cambiato da `'Assets'` a `'admin'` in `Html::header()` di server.php e server.form.php -- Mostra: Pagina principale > Amministrazione > UrBackup servers - -**Fix port default per nuovi server**: -- `prepareInputForAdd()` ora setta default 55414 per `port`, 'http' per `protocol`, 1 per `is_active`, ecc. -- `prepareInputForUpdate()` normalizza anche valori vuoti - -**File modificati**: -- `src/Server.php`: `showFormFields()`, `showMissingClientsTab()`, `getCachedName()` fix, `getAssetGroupName()`, `getAssetIp()`, `getCachedLocationName()`, `prepareInputForAdd/Update` default fix, icona `ti-cloud-up` -- `front/server.form.php`: Tab laterali, tab hash JS, breadcrumb admin, navigazione server -- `front/server.php`: Breadcrumb admin, rimosso pulsante "Add" manuale -- `src/Profile.php`: Icona `ti-cloud-up` -- `src/AssetTab.php`: Icona `ti-cloud-up` -- **Permessi**: `front/server.form.php` ora usa READ invece di UPDATE per accesso form; View search option + pulsanti Connect nascosti per READ; API username/password nascosti per READ -- **i18n**: aggiunte 17 stringhe mancanti con dominio `urbackup` a tutti i file e .po/.mo (it, en, de) -- **Bug fix**: ServerAsset colonne rimosse, asset.form.php usa disconnectAsset(), `declare(strict_types=1)` in 11 file +### v0.5.1 — Fix registrazione tab su Asset Definition nuovi +- **Bug**: Tab UrBackup non appariva su Asset Definition nuovi (es. `testnas`) +- **Causa**: `getDefinitions()` tornava 0 in `plugin_init_urbackup()` perché `bootDefinitions()` gira dopo (priority 100 vs 110) +- **Fix**: Spostata registrazione tab in `UrBackupCapacity::onClassBootstrap()` — chiamata durante `bootDefinitions()` per ogni definizione con capacità abilitata +- Rimossi debug logging superflui da `setup.php` e `AssetTab.php` diff --git a/front/asset.form.php b/front/asset.form.php index 3046d20..db184e4 100644 --- a/front/asset.form.php +++ b/front/asset.form.php @@ -8,6 +8,7 @@ declare(strict_types=1); * ------------------------------------------------------------------------- */ +use Glpi\Exception\Http\BadRequestHttpException; use GlpiPlugin\Urbackup\AssetTab; use GlpiPlugin\Urbackup\Config; use GlpiPlugin\Urbackup\Profile; @@ -27,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', 'urbackup')); + throw new BadRequestHttpException(__('Invalid parameters', 'urbackup')); } if (!in_array($itemtype, Config::getEnabledItemtypes(), true)) { - Html::displayValidationError(__('Item type not enabled for UrBackup', 'urbackup')); + throw new BadRequestHttpException(__('Item type not enabled for UrBackup', 'urbackup')); } $item = getItemForItemtype($itemtype); @@ -48,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', 'urbackup')); + 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', 'urbackup')); + Session::addMessageAfterRedirect( + __('Asset is already linked to a server', 'urbackup'), + false, + ERROR + ); + Html::redirect($item->getLinkURL()); } $result = ServerAsset::createForAsset($itemtype, $items_id, $server_id); @@ -63,7 +74,12 @@ if (isset($_POST['connect'])) { $item->getFromDB($items_id); Html::redirect($item->getLinkURL()); } else { - Html::displayValidationError(__('Failed to link asset to server', 'urbackup')); + Session::addMessageAfterRedirect( + __('Failed to link asset to server', 'urbackup'), + false, + ERROR + ); + Html::redirect($item->getLinkURL()); } } @@ -78,7 +94,12 @@ if (isset($_POST['disconnect'])) { $item->getFromDB($items_id); Html::redirect($item->getLinkURL()); } else { - Html::displayValidationError(__('Failed to disconnect asset from server', 'urbackup')); + Session::addMessageAfterRedirect( + __('Failed to disconnect asset from server', 'urbackup'), + false, + ERROR + ); + Html::redirect($item->getLinkURL()); } } diff --git a/install/install.php b/install/install.php index 44c1c81..4473388 100644 --- a/install/install.php +++ b/install/install.php @@ -8,6 +8,8 @@ declare(strict_types=1); * ------------------------------------------------------------------------- */ +use Glpi\Asset\AssetDefinitionManager; +use GlpiPlugin\Urbackup\Capacity\UrBackupCapacity; use GlpiPlugin\Urbackup\Config; use GlpiPlugin\Urbackup\Profile; @@ -30,6 +32,10 @@ function plugin_urbackup_install_process(): bool 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); @@ -153,11 +159,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'); @@ -338,3 +339,55 @@ function plugin_urbackup_install_update_profiles_table(Migration $migration): vo $migration->addKey($table, ['profiles_id', 'rightname'], 'profile_right'); } + +/** + * 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; + } + + // 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; + } + } + + // 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() + ); + } + } + + // Drop is_default column (no longer needed in capacity system) + if ($has_is_default) { + $migration->dropField($table, 'is_default'); + } +} diff --git a/install/mysql/plugin_urbackup-empty.sql b/install/mysql/plugin_urbackup-empty.sql index 3562526..52e7a86 100644 --- a/install/mysql/plugin_urbackup-empty.sql +++ b/install/mysql/plugin_urbackup-empty.sql @@ -12,7 +12,6 @@ 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`), diff --git a/setup.php b/setup.php index 73e14e0..f1b7c70 100644 --- a/setup.php +++ b/setup.php @@ -2,20 +2,30 @@ declare(strict_types=1); -// Force OPcache to reload this file when accessed via web +// Force OPcache to reload plugin files when accessed via web if (PHP_SAPI !== 'cli' && function_exists('opcache_invalidate')) { opcache_invalidate(__FILE__, true); + $capacity_file = __DIR__ . '/src/Capacity/UrBackupCapacity.php'; + if (file_exists($capacity_file)) { + opcache_invalidate($capacity_file, true); + } + $asset_tab_file = __DIR__ . '/src/AssetTab.php'; + if (file_exists($asset_tab_file)) { + opcache_invalidate($asset_tab_file, 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.5.0'); define('PLUGIN_URBACKUP_MIN_GLPI', '11.0.0'); define('PLUGIN_URBACKUP_MAX_GLPI', '11.99.99'); @@ -52,12 +62,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'; @@ -75,18 +92,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(); } /** diff --git a/src/AssetTab.php b/src/AssetTab.php index 077591f..7037760 100644 --- a/src/AssetTab.php +++ b/src/AssetTab.php @@ -57,11 +57,15 @@ 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 ''; } diff --git a/src/Capacity/UrBackupCapacity.php b/src/Capacity/UrBackupCapacity.php new file mode 100644 index 0000000..414c787 --- /dev/null +++ b/src/Capacity/UrBackupCapacity.php @@ -0,0 +1,62 @@ +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) + ); + } +} diff --git a/src/Config.php b/src/Config.php index bd9ecfd..e4a99a5 100644 --- a/src/Config.php +++ b/src/Config.php @@ -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 "
"; echo "
"; echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; + echo ""; + echo ""; + echo ""; + echo ""; echo ""; - - foreach (self::getConfigurableAssetTypes() as $itemtype => $label) { - $enabled = self::isItemtypeEnabled($itemtype); - $is_default = ($itemtype === 'Computer'); - $is_asset_definition = self::isAssetDefinition($itemtype); - - echo ""; - echo ""; - - if ($itemtype === 'Computer') { - echo ""; - echo ""; - } elseif ($is_asset_definition) { - // GLPI 11 Asset Definition - echo ""; - echo ""; - echo ""; - } else { - // Legacy type - echo ""; - echo ""; - echo ""; - } - - echo ""; - } - - echo ""; - echo ""; - echo ""; - echo "
" . htmlspecialchars(__('UrBackup configuration', 'urbackup')) . "
" . htmlspecialchars(__('Asset type', 'urbackup')) . "" . htmlspecialchars(__('Enabled', 'urbackup')) . "" . htmlspecialchars(__('Default', 'urbackup')) . "" . htmlspecialchars(__('Type', 'urbackup')) . "
" . htmlspecialchars(__('UrBackup configuration', 'urbackup')) . "
" . htmlspecialchars(__('Computer', 'urbackup')) . "" . htmlspecialchars(__('Always enabled', 'urbackup')) . "
" . htmlspecialchars($label) . "" . htmlspecialchars(__('Always', 'urbackup')) . "" . htmlspecialchars(__('Yes', 'urbackup')) . ""; - Html::showCheckbox([ - 'name' => 'assettypes[' . htmlspecialchars($itemtype) . '][is_active]', - 'checked' => $enabled, - ]); - echo ""; - Html::showCheckbox([ - 'name' => 'assettypes[' . htmlspecialchars($itemtype) . '][is_default]', - 'checked' => $is_default, - ]); - echo "" . htmlspecialchars(__('Asset Definition', 'urbackup')) . ""; - Html::showCheckbox([ - 'name' => 'assettypes[' . htmlspecialchars($itemtype) . ']', - 'checked' => $enabled, - ]); - echo "" . ($is_default ? htmlspecialchars(__('Yes', 'urbackup')) : '') . "" . htmlspecialchars(__('Legacy', 'urbackup')) . "
"; - echo Html::submit(__('Save', 'urbackup'), [ - 'name' => 'update', - 'class' => 'btn btn-primary', - ]); - echo "
"; - echo "
"; - Html::closeForm(); + echo "
"; + echo "
"; + echo htmlspecialchars( + __('For Asset Definition types, enable/disable UrBackup via Config > Asset definitions > Capacities.', 'urbackup') + ); + echo "
"; + echo ""; 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 $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 - */ - 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; } } \ No newline at end of file diff --git a/src/Controller/ConfigController.php b/src/Controller/ConfigController.php index a0e5cf0..1db03e4 100644 --- a/src/Controller/ConfigController.php +++ b/src/Controller/ConfigController.php @@ -31,15 +31,6 @@ class ConfigController extends AbstractController 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, - ]); + return $this->render('config/config.html.twig', []); } } diff --git a/src/UrbackupApiClient.php b/src/UrbackupApiClient.php index 8a9f9c6..04e54d1 100644 --- a/src/UrbackupApiClient.php +++ b/src/UrbackupApiClient.php @@ -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 = ''; @@ -667,6 +669,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 => [ diff --git a/templates/config/config.html.twig b/templates/config/config.html.twig index a3011e8..1c3bb16 100644 --- a/templates/config/config.html.twig +++ b/templates/config/config.html.twig @@ -2,38 +2,17 @@
- + - - - - - - {% for itemtype, label in configurable_types %} - - - {% if itemtype == 'Computer' %} - - {% else %} - - {% endif %} - - - {% endfor %} - - + + +
{{ __('UrBackup configuration', 'urbackup') }}{{ __('UrBackup configuration', 'urbackup') }}
{{ __('Asset type', 'urbackup') }}{{ __('Enabled', 'urbackup') }}{{ __('Default', 'urbackup') }}
{{ label }}{{ __('Always enabled', 'urbackup') }} - - - {% if itemtype == 'Computer' %} - {{ __('Yes', 'urbackup') }} - {% endif %} -
- -
{{ __('Computer', 'urbackup') }}{{ __('Always enabled', 'urbackup') }}
+
+
+ {{ __('For Asset Definition types, enable/disable UrBackup via Config > Asset definitions > Capacities.', 'urbackup') }} +