re-check final

This commit is contained in:
2026-06-01 22:55:50 +02:00
parent 7bdbf4e077
commit 04ba48e87b
136 changed files with 1362 additions and 21579 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
@extends('layouts.adminlte')
@extends('admin.layout')
@php $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree'; @endphp
@section('title', 'Backup - ' . $appName)
@section('page_title', 'Backup e Migrazione')
@section('breadcrumbs')
+6
View File
@@ -59,6 +59,12 @@
<p>Ruoli</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/backup" class="nav-link {{ request()->is('admin/backup*') ? 'active' : '' }}">
<i class="nav-icon fas fa-hdd"></i>
<p>Backup</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/activity-logs" class="nav-link {{ request()->is('admin/activity-logs*') ? 'active' : '' }}">
<i class="nav-icon fas fa-history"></i>
+90 -32
View File
@@ -500,7 +500,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- MODAL: Nuova Cartella --}}
<div class="modal fade" id="newFolderModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-folder-plus mr-2"></i>Nuova Cartella</h5>
@@ -535,7 +535,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- MODAL: Upload --}}
<div class="modal fade" id="uploadModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-upload mr-2"></i>Carica Documento</h5>
@@ -543,9 +543,9 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
<span>&times;</span>
</button>
</div>
<form action="/documenti" method="POST" enctype="multipart/form-data">
@csrf
<div class="modal-body">
<div class="modal-body">
<form action="/documenti" method="POST" enctype="multipart/form-data" id="uploadForm">
@csrf
<div class="form-group">
<label>Nome *</label>
<input type="text" name="nome_file" class="form-control" placeholder="Nome del documento" required>
@@ -557,11 +557,15 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
<option value="{{ $tip->nome }}">{{ $tip->descrizione ?? ucfirst($tip->nome) }}</option>
@endforeach
</select>
<button type="button" class="btn btn-sm btn-outline-success mt-2" id="massMoveNewFolderBtn">
<i class="fas fa-plus mr-1"></i> Nuova cartella
</button>
</div>
</div>
<div class="form-group">
<label>Visibilità *</label>
<select name="visibilita" id="uploadVisibilita" class="form-control" onchange="loadUploadTargets()">
<option value="pubblico">Pubblico</option>
<option value="individuo">Individuo</option>
<option value="gruppo">Gruppo</option>
</select>
</div>
<div class="form-group" id="uploadTargetGroup" style="display:none;">
<label>Riferimento</label>
<select name="visibilita_target_id" class="form-control" id="uploadTargetId">
@@ -589,29 +593,42 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
<input type="file" name="file" class="form-control" required>
<small class="text-muted">Max 10MB</small>
</div>
@if($currentFolder && !$currentRepo)
<input type="hidden" name="cartella_id" value="{{ $currentFolder->id }}">
<p class="text-muted small mb-0">Il documento sarà inserito in: <strong>{{ $currentFolder->nome }}</strong></p>
@endif
<div class="form-group" id="uploadFolderGroup">
<label>Cartella</label>
<select name="cartella_id" id="uploadCartellaId" class="form-control">
<option value=""> Nessuna cartella </option>
@foreach($cartelle as $cartella)
<option value="{{ $cartella->id }}" {{ $currentFolder && $currentFolder->id === $cartella->id ? 'selected' : '' }}>{{ $cartella->nome }}</option>
@if($cartella->children->count() > 0)
@foreach($cartella->children as $child)
<option value="{{ $child->id }}" {{ $currentFolder && $currentFolder->id === $child->id ? 'selected' : '' }}>&nbsp;&nbsp;&nbsp;{{ $child->nome }}</option>
@endforeach
@endif
@endforeach
</select>
<button type="button" class="btn btn-sm btn-outline-success mt-2" id="uploadNewFolderBtn">
<i class="fas fa-plus mr-1"></i> Nuova cartella
</button>
</div>
@if($currentRepo)
<input type="hidden" name="repository_id" value="{{ $currentRepo->id }}">
<p class="text-muted small mb-0">Caricamento su repository remoto: <strong>{{ $currentRepo->nome }}</strong></p>
@endif
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="$('#uploadModal').modal('hide')">Annulla</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-upload mr-1"></i> Carica
</button>
</div>
</form>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="$('#uploadModal').modal('hide')">Annulla</button>
<button type="submit" class="btn btn-primary" form="uploadForm">
<i class="fas fa-upload mr-1"></i> Carica
</button>
</div>
</div>
</div>
</div>
{{-- MODAL: Associa --}}
<div class="modal fade" id="associateModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-link mr-2"></i>Associa a</h5>
@@ -651,7 +668,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- MODAL: Cambia tipo/contesto --}}
<div class="modal fade" id="updateModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-edit mr-2"></i>Cambia tipo/contesto</h5>
@@ -727,7 +744,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- MODAL: Conferma eliminazione singola --}}
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title"><i class="fas fa-exclamation-triangle mr-2"></i>Conferma Eliminazione</h5>
@@ -751,7 +768,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- MODAL: Conferma eliminazione multipla --}}
<div class="modal fade" id="massDeleteConfirmModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title"><i class="fas fa-exclamation-triangle mr-2"></i>Conferma Eliminazione Multipla</h5>
@@ -775,7 +792,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- MODAL: Sposta massivo --}}
<div class="modal fade" id="massMoveModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-folder-open mr-2"></i>Sposta documenti selezionati</h5>
@@ -801,6 +818,9 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
@endif
@endforeach
</select>
<button type="button" class="btn btn-sm btn-outline-success mt-2" id="massMoveNewFolderBtn">
<i class="fas fa-plus mr-1"></i> Nuova cartella
</button>
</div>
</div>
<div class="modal-footer">
@@ -816,7 +836,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- MODAL: Sposta documento --}}
<div class="modal fade" id="moveModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-folder-open mr-2"></i>Sposta documento</h5>
@@ -864,6 +884,21 @@ const eventi = @json($eventi->map(fn($e) => ['id' => $e->id, 'label' => $e->nome
const mailingLists = @json($mailingLists ? $mailingLists->map(fn($m) => ['id' => $m->id, 'label' => $m->nome]) : []);
const cartelle = @json($cartelleMoveOptions);
$('#uploadModal').on('shown.bs.modal', function () {
loadUploadTargets();
toggleUploadRepo();
});
$('#newFolderModal').on('show.bs.modal', function () {
$(this).css('z-index', 1060);
$('.modal-backdrop:last').css('z-index', 1050);
});
$('#newFolderModal').on('hidden.bs.modal', function () {
window._uploadNewFolder = false;
$(this).css('z-index', '');
});
function toggleAll(source) {
document.querySelectorAll('.doc-checkbox').forEach(cb => cb.checked = source.checked);
updateCount();
@@ -1244,6 +1279,13 @@ document.getElementById('massMoveNewFolderBtn')?.addEventListener('click', funct
});
});
document.getElementById('uploadNewFolderBtn')?.addEventListener('click', function(e) {
e.preventDefault();
window._uploadNewFolder = true;
$('#uploadModal').modal('hide');
$('#newFolderModal').modal('show');
});
document.getElementById('massMoveForm')?.addEventListener('submit', function(e) {
e.preventDefault();
const form = this;
@@ -1336,8 +1378,21 @@ document.getElementById('newFolderForm')?.addEventListener('submit', function(e)
.then(response => response.json())
.then(data => {
if (data.success) {
$('#newFolderModal').modal('hide');
window.location.reload();
if (window._uploadNewFolder) {
window._uploadNewFolder = false;
$('#newFolderModal').modal('hide');
$('#uploadModal').modal('show');
const select = document.getElementById('uploadCartellaId');
if (select) {
const opt = document.createElement('option');
opt.value = data.cartella.id;
opt.textContent = data.cartella.nome;
opt.selected = true;
select.appendChild(opt);
}
} else {
window.location.reload();
}
} else {
alert('Errore: ' + (data.message || 'Impossibile creare la cartella'));
}
@@ -1349,11 +1404,14 @@ document.getElementById('newFolderForm')?.addEventListener('submit', function(e)
function toggleUploadRepo() {
const repoSelect = document.getElementById('uploadRepository');
const cartellaInput = document.querySelector('input[name="cartella_id"]');
const folderGroup = document.getElementById('uploadFolderGroup');
const cartellaSelect = document.getElementById('uploadCartellaId');
if (repoSelect && repoSelect.value !== '') {
if (cartellaInput) cartellaInput.disabled = true;
if (folderGroup) folderGroup.style.display = 'none';
if (cartellaSelect) cartellaSelect.disabled = true;
} else {
if (cartellaInput) cartellaInput.disabled = false;
if (folderGroup) folderGroup.style.display = '';
if (cartellaSelect) cartellaSelect.disabled = false;
}
}
+6
View File
@@ -1,5 +1,6 @@
@extends('layouts.adminlte')
@php $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree'; @endphp
@section('title', 'Guida - ' . $appName)
@section('page_title', 'Guida alla Configurazione')
@section('breadcrumbs')
@@ -53,6 +54,11 @@
</li>
</ul>
</div>
<div class="card-footer">
<a href="{{ route('help.pdf.download') }}" class="btn btn-primary btn-block">
<i class="fas fa-file-pdf mr-2"></i> Scarica Guida PDF
</a>
</div>
</div>
</div>
+894
View File
@@ -0,0 +1,894 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<title>{{ $appName ?? 'Glastree' }} Guida alla Configurazione</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 11pt;
line-height: 1.6;
color: #333;
padding: 30px 40px;
}
h1 { font-size: 22pt; color: #1a237e; margin-bottom: 5px; }
h2 { font-size: 16pt; color: #283593; margin-top: 28px; margin-bottom: 10px; border-bottom: 2px solid #e0e0e0; padding-bottom: 4px; }
h3 { font-size: 13pt; color: #303f9f; margin-top: 20px; margin-bottom: 8px; }
h4 { font-size: 12pt; color: #3949ab; margin-top: 16px; margin-bottom: 6px; }
h5 { font-size: 11pt; color: #5c6bc0; margin-top: 14px; margin-bottom: 5px; }
h6 { font-size: 11pt; color: #5c6bc0; margin-top: 12px; margin-bottom: 4px; }
p { margin-bottom: 8px; }
ul, ol { margin: 6px 0 10px 20px; }
li { margin-bottom: 3px; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 10pt; }
th, td { border: 1px solid #ccc; padding: 6px 8px; text-align: left; }
th { background: #e8eaf6; font-weight: 600; }
code { background: #f5f5f5; padding: 1px 5px; border-radius: 3px; font-family: 'Consolas', 'Courier New', monospace; font-size: 9.5pt; color: #c62828; }
pre { background: #263238; color: #e0e0e0; padding: 12px 16px; border-radius: 5px; font-family: 'Consolas', 'Courier New', monospace; font-size: 9pt; line-height: 1.4; overflow-x: auto; margin: 8px 0 14px; white-space: pre-wrap; word-break: break-word; }
pre code { background: none; color: inherit; padding: 0; font-size: inherit; }
.callout { border-left: 4px solid; padding: 10px 14px; margin: 12px 0; background: #f5f5f5; border-radius: 0 4px 4px 0; }
.callout-info { border-color: #2196f3; background: #e3f2fd; }
.callout-danger { border-color: #f44336; background: #ffebee; }
.callout-warning { border-color: #ff9800; background: #fff3e0; }
.callout-success { border-color: #4caf50; background: #e8f5e9; }
.badge { display: inline-block; padding: 2px 7px; font-size: 9pt; border-radius: 3px; background: #e0e0e0; }
.badge-primary { background: #1976d2; color: #fff; }
.badge-success { background: #388e3c; color: #fff; }
.badge-info { background: #0288d1; color: #fff; }
.badge-secondary { background: #757575; color: #fff; }
.subtitle { font-size: 12pt; color: #666; margin-bottom: 20px; }
.page-break { page-break-before: always; }
hr { border: none; border-top: 1px solid #ddd; margin: 24px 0; }
.toc { margin: 20px 0; padding: 16px 20px; background: #f5f5f5; border-radius: 5px; }
.toc a { color: #1565c0; text-decoration: none; }
.toc li { margin-bottom: 5px; }
@media print {
body { padding: 0; font-size: 10pt; }
pre { font-size: 8pt; padding: 8px 12px; }
h2 { page-break-after: avoid; }
h3, h4, h5 { page-break-after: avoid; }
.callout, table, pre { page-break-inside: avoid; }
}
</style>
</head>
<body>
<h1>{{ $appName ?? 'Glastree' }}</h1>
<p class="subtitle">Guida completa alla configurazione, installazione e migrazione</p>
<div class="toc">
<strong>Indice</strong>
<ol>
<li><a href="#webdav">WebDAV / Nextcloud</a></li>
<li><a href="#googledrive">Google Drive</a></li>
<li><a href="#tipologie">Tipologie e Gestione Dati</a></li>
<li><a href="#email">Email / SMTP</a></li>
<li><a href="#installazione">Guida all'Installazione</a></li>
<li><a href="#backup">Backup e Migrazione Server</a></li>
<li><a href="#docker">Docker</a></li>
</ol>
</div>
{{-- 1. WebDAV / Nextcloud --}}
<h2 id="webdav">1. WebDAV / Nextcloud</h2>
<h3>Cos'è</h3>
<p>WebDAV è un protocollo che permette di accedere a file su server remoti. Nextcloud lo utilizza per la sincronizzazione dei file.</p>
<h3>Configurazione</h3>
<p>Vai in <strong>Impostazioni Repository</strong> e crea un nuovo repository di tipo WebDAV.</p>
<div class="callout callout-info">
<h4>Campi obbligatori</h4>
<dl>
<dt><strong>Nome</strong></dt>
<dd>Un nome descrittivo (es. "Nextcloud Lavoro")</dd>
<dt><strong>Base URI</strong></dt>
<dd>L'URL del tuo server WebDAV.<br>
<em>Nextcloud:</em> <code>https://tuodominio.it/remote.php/dav/files/utente/</code><br>
<em>Altri:</em> <code>https://server.webdav.it/</code></dd>
<dt><strong>Root</strong></dt>
<dd>Percorso base all'interno del server (es. <code>/Documenti/</code>). Lascia <code>/</code> per usare la root.</dd>
<dt><strong>Username</strong></dt>
<dd>Il tuo nome utente</dd>
<dt><strong>Password</strong></dt>
<dd>La password dell'account</dd>
<dt><strong>Auth Type</strong></dt>
<dd><code>Basic</code> per la maggior parte dei server (Nextcloud, ownCloud).</dd>
</dl>
</div>
<h3>Nome utente con @ (chiocciola)</h3>
<p>Se il tuo nome utente contiene il carattere <code>@</code> (es. <code>mario.rossi@azienda.com</code>), segui queste regole:</p>
<ul>
<li><strong>Nel campo "Base URI":</strong> l'indirizzo email deve essere <strong>URL-encoded</strong>:<br>
<code>https://server.it/remote.php/dav/files/mario.rossi%40azienda.com/</code><br>
<small>(il carattere <code>@</code> diventa <code>%40</code>)</small></li>
<li><strong>Nel campo "Username":</strong> inserisci l'indirizzo email <strong>normale</strong> con la <code>@</code> vera.</li>
</ul>
<p>Il sistema gestisce automaticamente la differenza tra l'URI (che richiede <code>%40</code>) e l'autenticazione (che usa la <code>@</code> reale).</p>
<h3>Test connessione</h3>
<p>Dopo aver salvato, usa il pulsante <strong>Test</strong> nella tabella dei repository per verificare che la connessione funzioni.</p>
<h3>Importare file</h3>
<p>Apri la sezione <strong>Documenti</strong>, clicca sul pulsante del repository nella toolbar per sfogliare i file remoti, poi clicca <strong>Salva tra i documenti</strong> su un file per importarlo come documento locale.</p>
<div class="page-break"></div>
{{-- 2. Google Drive --}}
<h2 id="googledrive">2. Google Drive</h2>
<h3>Cos'è</h3>
<p>Integrazione con Google Drive per sfogliare e importare file direttamente dal tuo spazio Google Drive.</p>
<h3>Requisiti</h3>
<ol>
<li>Un account Google / Google Workspace</li>
<li>Un progetto sulla <strong>Google Cloud Console</strong> con l'API Google Drive abilitata</li>
<li>Credenziali OAuth 2.0 (Client ID e Client Secret)</li>
</ol>
<h3>Passo 1: Creare il progetto Google Cloud</h3>
<ol>
<li>Vai su <strong>Google Cloud Console</strong> (console.developers.google.com)</li>
<li>Crea un nuovo progetto o selezionane uno esistente</li>
<li>Vai a <strong>API e servizi → Libreria</strong></li>
<li>Cerca "<strong>Google Drive API</strong>" e <strong>abilitala</strong></li>
<li>Vai a <strong>API e servizi → Credenziali</strong></li>
<li>Clicca "<strong>Crea credenziali → ID client OAuth</strong>"</li>
<li>Tipo applicazione: "<strong>Applicazione Web</strong>"</li>
<li><strong>URI di reindirizzamento autorizzato:</strong> aggiungi <code>{{ url('/auth/google-drive/callback') }}</code></li>
<li>Completa la creazione e <strong>copia Client ID e Client Secret</strong></li>
</ol>
<h3>Passo 2: Configurazione nell'app</h3>
<ol>
<li>Vai in <strong>Impostazioni Repository</strong></li>
<li>Clicca "<strong>Nuovo Repository</strong>" e seleziona tipo <strong>Google Drive</strong></li>
<li>Inserisci un nome descrittivo (es. "Drive Lavoro")</li>
<li>Incolla <strong>Client ID</strong> e <strong>Client Secret</strong></li>
<li><strong>Folder ID (opzionale):</strong> se vuoi limitare l'accesso a una cartella specifica, inserisci il suo ID (lo trovi nell'URL quando apri la cartella in Google Drive: <code>https://drive.google.com/drive/folders/<strong>ID_QUI</strong></code>)</li>
<li>Clicca "<strong>Autorizza Google Drive</strong>" verrai reindirizzato a Google per concedere i permessi</li>
<li>Dopo l'autorizzazione, il refresh token verrà salvato automaticamente</li>
</ol>
<h3>Google Drive API non abilitata</h3>
<p>Se vedi l'errore <em>"Google Drive API non abilitata"</em> nel test connessione, significa che l'API Drive non è stata attivata nel tuo progetto Google Cloud.</p>
<div class="callout callout-danger">
<h4>Soluzione</h4>
<p>Vai su <code>https://console.developers.google.com/apis/api/drive.googleapis.com/overview</code></p>
<p>Seleziona il progetto corretto dall'header in alto, clicca <strong>"Enable"</strong> e attendi qualche minuto prima di riprovare.</p>
</div>
<h3>Importare file</h3>
<p>Stessa procedura di WebDAV: apri <strong>Documenti</strong>, clicca sul repository Drive, sfoglia i file e usa <strong>Salva tra i documenti</strong>.</p>
<h3>File Google (Documenti, Fogli, Presentazioni)</h3>
<p>I file creati con Google Workspace (Documenti Google, Fogli Google, Presentazioni Google, Disegni Google) non possono essere scaricati direttamente perché non hanno un formato binario nativo.</p>
<p>Il sistema li converte automaticamente in formato Office Open XML durante l'import:</p>
<table>
<thead><tr><th>File Google</th><th>Formato convertito</th></tr></thead>
<tbody>
<tr><td>Documento Google</td><td><code>.docx</code> (Word)</td></tr>
<tr><td>Foglio Google</td><td><code>.xlsx</code> (Excel)</td></tr>
<tr><td>Presentazione Google</td><td><code>.pptx</code> (PowerPoint)</td></tr>
<tr><td>Disegno Google</td><td><code>.png</code> (Immagine)</td></tr>
<tr><td>Altri (Moduli, Script)</td><td><code>.pdf</code> (PDF)</td></tr>
</tbody>
</table>
<p>Il nome del file viene automaticamente esteso con l'estensione corretta (es. "Relazione" "Relazione.docx").</p>
<div class="page-break"></div>
{{-- 3. Tipologie e Dati --}}
<h2 id="tipologie">3. Tipologie e Gestione Dati</h2>
<h3>Tipologie Documenti</h3>
<p>Le tipologie classificano i documenti (avatar, galleria, documento, statuto, altro).</p>
<p>Si gestiscono in <strong>Impostazioni Tipologie Documenti</strong>. Puoi aggiungere, rinominare, riordinare ed eliminare tipologie (tranne quelle con documenti associati).</p>
<h3>Tipologie Individui</h3>
<p>Classificano gli individui (es. socio, volontario, dipendente). Si gestiscono in <strong>Impostazioni Tipologie</strong>.</p>
<h3>Tipologie Eventi</h3>
<p>Classificano gli eventi (es. riunione, assemblea, formazione). Si gestiscono in <strong>Impostazioni Tipologie Eventi</strong>.</p>
<h3>Ruoli Individui</h3>
<p>Definiscono il ruolo o la carica di un individuo (es. presidente, segretario, tesoriere). Si gestiscono in <strong>Impostazioni Ruoli</strong>.</p>
<h3>Visibilità Documenti</h3>
<p>Ogni documento può avere diversi livelli di visibilità:</p>
<ul>
<li><strong>Pubblico</strong> visibile a tutti gli utenti</li>
<li><strong>Individuo</strong> visibile solo a uno specifico individuo</li>
<li><strong>Gruppo</strong> visibile solo ai membri di un gruppo</li>
<li><strong>Evento</strong> visibile solo ai partecipanti di un evento</li>
<li><strong>Mailing</strong> visibile solo ai membri di una mailing list</li>
</ul>
<div class="page-break"></div>
{{-- 4. Email / SMTP --}}
<h2 id="email">4. Email / SMTP</h2>
<h3>Configurazione Email</h3>
<p>Vai in <strong>Impostazioni Email</strong> per configurare la posta elettronica.</p>
<h3>Server IMAP (Ricezione)</h3>
<p>Usato per ricevere e leggere le email direttamente nell'app.</p>
<div class="callout callout-info">
<h4>Campi consigliati per provider comuni</h4>
<table>
<thead><tr><th>Provider</th><th>Host</th><th>Porta</th><th>Crittografia</th></tr></thead>
<tbody>
<tr><td>Gmail</td><td><code>imap.gmail.com</code></td><td><code>993</code></td><td>SSL</td></tr>
<tr><td>Outlook/Office 365</td><td><code>outlook.office365.com</code></td><td><code>993</code></td><td>SSL</td></tr>
<tr><td>Libero</td><td><code>imapmail.libero.it</code></td><td><code>993</code></td><td>SSL</td></tr>
<tr><td>Aruba</td><td><code>imap.aruba.it</code></td><td><code>993</code></td><td>SSL</td></tr>
</tbody>
</table>
</div>
<h3>Server SMTP (Invio)</h3>
<p>Usato per inviare email dall'app. Supporta account multipli (mittenti).</p>
<div class="callout callout-info">
<h4>Campi consigliati per provider comuni</h4>
<table>
<thead><tr><th>Provider</th><th>Host</th><th>Porta</th><th>Crittografia</th></tr></thead>
<tbody>
<tr><td>Gmail</td><td><code>smtp.gmail.com</code></td><td><code>587</code></td><td>TLS</td></tr>
<tr><td>Outlook/Office 365</td><td><code>smtp.office365.com</code></td><td><code>587</code></td><td>TLS</td></tr>
<tr><td>Libero</td><td><code>smtp.libero.it</code></td><td><code>465</code></td><td>SSL</td></tr>
<tr><td>Aruba</td><td><code>smtp.aruba.it</code></td><td><code>465</code></td><td>SSL</td></tr>
</tbody>
</table>
</div>
<h3>Gmail / Google Workspace</h3>
<div class="callout callout-warning">
Gmail richiede una <strong>Password per l'app</strong> (non la password normale) se usi la verifica in due passaggi. Puoi generarla su <code>https://myaccount.google.com/apppasswords</code>.
</div>
<h3>Mailing</h3>
<p>Le <strong>Mailing List</strong> permettono di inviare email a gruppi predefiniti di destinatari.</p>
<ul>
<li>Crea una mailing list in <strong>Mailing List → Nuova</strong></li>
<li>Puoi popolare la lista da individui, gruppi o manualmente</li>
<li>Invia email alla lista da <strong>Email → Nuova Email</strong>, selezionando la mailing list come destinatario</li>
</ul>
<h3>Test connessione</h3>
<p>Dopo aver configurato i server, usa il pulsante <strong>Test Connessione</strong> per verificare che IMAP e SMTP funzionino correttamente.</p>
<div class="page-break"></div>
{{-- 5. Installazione --}}
<h2 id="installazione">5. Guida all'Installazione</h2>
<h3>Panoramica</h3>
<p>L'applicazione include un <strong>wizard di installazione web</strong> che guida passo-passo nella configurazione su un nuovo server LAMP. Supporta due modalità:</p>
<table>
<thead><tr><th>Modalità</th><th>Descrizione</th></tr></thead>
<tbody>
<tr><td><strong>Fresh Install</strong></td><td>Installa l'applicazione da zero su un nuovo server: crea il database, esegue le migration, popola i dati di base e crea l'amministratore.</td></tr>
<tr><td><strong>Restore da Backup</strong></td><td>Ripristina un'installazione esistente da un file ZIP di backup: importa il database, ripristina i documenti e la configurazione.</td></tr>
</tbody>
</table>
<h3>Requisiti del Server</h3>
<table>
<thead><tr><th>Componente</th><th>Versione Minima</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Web Server</td><td>Apache 2.4+</td><td>Con mod_rewrite abilitato</td></tr>
<tr><td>PHP</td><td>8.2+ (consigliato 8.4)</td><td>Estensioni: pdo_mysql, mbstring, xml, curl, zip, gd, fileinfo</td></tr>
<tr><td>MySQL / MariaDB</td><td>MySQL 8.0+ / MariaDB 10.5+</td><td>Supporto utf8mb4</td></tr>
<tr><td>Composer</td><td>2.x</td><td>Per gestione dipendenze PHP</td></tr>
<tr><td>Node.js / npm</td><td>Node 20+ / npm 10+</td><td>Per compilazione asset frontend (opzionale se precompilati)</td></tr>
</tbody>
</table>
<h3>Installazione Passo-Passo</h3>
<h4>Passo 1: Preparare il server LAMP</h4>
<pre>sudo apt update && sudo apt upgrade -y
sudo apt install -y apache2 mariadb-server php8.4 php8.4-cli \
php8.4-mysql php8.4-xml php8.4-mbstring php8.4-curl \
php8.4-zip php8.4-bcmath php8.4-gd php8.4-intl \
composer git unzip
sudo a2enmod rewrite
sudo systemctl restart apache2</pre>
<h4>Passo 2: Ottenere l'applicazione</h4>
<p>Puoi clonare il repository con git oppure estrarre il file ZIP ricevuto nella cartella <code>/var/www/glastree</code>.</p>
<pre>cd /var/www
# Opzione A — Git clone
git clone &lt;URL_REPOSITORY&gt; glastree
# Opzione B — ZIP (se non hai git)
# unzip /percorso/del/glastree.zip -d glastree
cd glastree
composer install --no-dev --optimize-autoloader
npm ci && npm run build</pre>
<p>Se non hai accesso a npm, puoi saltare il build. L'interfaccia userà comunque AdminLTE via CDN.</p>
<h4>Passo 3: Permessi cartelle</h4>
<pre>sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache</pre>
<h4>Passo 4: Configurare Apache</h4>
<pre>sudo tee /etc/apache2/sites-available/glastree.conf &lt;&lt;APACHE
&lt;VirtualHost *:80&gt;
ServerName glastree.esempio.it
DocumentRoot /var/www/glastree/public
&lt;Directory /var/www/glastree/public&gt;
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
&lt;/Directory&gt;
ErrorLog \${APACHE_LOG_DIR}/glastree_error.log
CustomLog \${APACHE_LOG_DIR}/glastree_access.log combined
&lt;/VirtualHost&gt;
APACHE
sudo a2dissite 000-default.conf
sudo a2ensite glastree.conf
sudo systemctl reload apache2</pre>
<h4>Passo 5: Avviare il wizard di installazione</h4>
<p>Apri il browser e visita: <code>http://glastree.esempio.it/installer/</code></p>
<p>Il wizard ti guiderà attraverso 6 passi:</p>
<table>
<thead><tr><th>Passo</th><th>Descrizione</th></tr></thead>
<tbody>
<tr><td>1. Benvenuto</td><td>Scegli Fresh Install o Restore da Backup</td></tr>
<tr><td>2. Requisiti</td><td>Verifica PHP, estensioni e permessi</td></tr>
<tr><td>3. Database</td><td>Configura MySQL (crea nuovo o usa esistente)</td></tr>
<tr><td>4. Installazione</td><td>Fresh: migration e seed. Backup: upload ZIP</td></tr>
<tr><td>5. Amministratore</td><td>Fresh: crea super-admin. Backup: verifica utenti</td></tr>
<tr><td>6. Finalizzazione</td><td>Cache, storage link, auto-eliminazione installer</td></tr>
</tbody>
</table>
<div class="callout callout-warning">
<strong>Attenzione:</strong> Al termine dell'installazione, la cartella <code>installer/</code> viene <strong>automaticamente eliminata</strong>. Non puoi rieseguire l'installazione se non ricreando manualmente la cartella.
</div>
<h3>Modalità Fresh Install</h3>
<p>Seleziona questa modalità quando installi l'applicazione per la prima volta su un server.</p>
<p>Cosa succede durante l'installazione:</p>
<ol>
<li>Genera <code>APP_KEY</code> per la crittografia</li>
<li>Esegue tutte le migration per creare le tabelle del database</li>
<li>Popola i dati di base (diocesi, comuni, ruoli, preset)</li>
<li>Crea il collegamento <code>storage public/storage</code></li>
<li>Crea l'utente amministratore con i permessi completi</li>
</ol>
<h3>Modalità Restore da Backup</h3>
<p>Seleziona questa modalità quando vuoi migrare un'installazione esistente su un nuovo server.</p>
<p>Cosa serve:</p>
<ul>
<li>Un file ZIP generato dalla pagina <strong>Admin Backup</strong> del server originale</li>
<li>Il wizard accetta upload diretto del file o un percorso sul server</li>
</ul>
<p>Cosa succede durante il restore:</p>
<ol>
<li>Estrae il file ZIP in una cartella temporanea</li>
<li>Importa <code>database.sql</code> nel database MySQL configurato</li>
<li>Copia i file documenti in <code>storage/app/documenti/</code></li>
<li>Ripristina il file <code>.env</code> dal backup (preservando le credenziali DB del nuovo server)</li>
</ol>
<h3>Dopo l'Installazione</h3>
<ol>
<li><strong>Configura SSL</strong> con Let's Encrypt:
<pre>sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d glastree.esempio.it</pre>
</li>
<li><strong>Accedi</strong> con le credenziali amministratore create durante l'installazione</li>
<li><strong>Configura l'applicazione</strong> in Impostazioni:
<ul>
<li>Nome applicazione e logo</li>
<li>Email (SMTP per invio, IMAP per ricezione)</li>
<li>Repository remoti (Google Drive, Nextcloud)</li>
</ul>
</li>
<li><strong>Crea utenti</strong> dal pannello Admin Utenti</li>
<li><strong>Configura il cron</strong> per backup automatico e sync email:
<pre>sudo crontab -u www-data -e
# Inserisci:
* * * * * cd /var/www/glastree && php artisan schedule:run >> /dev/null 2>&1</pre>
</li>
</ol>
<h3>Risoluzione Problemi</h3>
<div class="callout callout-danger">
<h4>Errore "500 | Server Error" dopo l'installazione</h4>
<p>Probabilmente mancano permessi sulle cartelle <code>storage</code> o <code>bootstrap/cache</code>:</p>
<pre>sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache</pre>
</div>
<div class="callout callout-danger">
<h4>Errore "No application encryption key"</h4>
<p>APP_KEY non generata. Nella cartella dell'app:</p>
<pre>php artisan key:generate</pre>
</div>
<div class="callout callout-warning">
<h4>mod_rewrite non funziona</h4>
<p>Se le rotte restituiscono 404, verifica che Apache abbia AllowOverride:</p>
<pre>sudo a2enmod rewrite
# Nel VirtualHost: AllowOverride All
sudo systemctl reload apache2</pre>
</div>
<div class="page-break"></div>
{{-- 6. Backup e Migrazione --}}
<h2 id="backup">6. Backup e Migrazione Server</h2>
<h3>Cos'è</h3>
<p>Il sistema di backup crea un archivio ZIP contenente tutto il necessario per ripristinare l'applicazione su un nuovo server. Include database, file caricati e configurazione.</p>
<h3>Cosa include il backup</h3>
<table>
<thead><tr><th>Componente</th><th>Incluso</th><th>Dettagli</th></tr></thead>
<tbody>
<tr><td>Database (MySQL)</td><td>Sempre</td><td>Struttura, dati, stored procedure, eventi, trigger</td></tr>
<tr><td>File caricati</td><td>Opzionale</td><td>Documenti, avatar, gallerie</td></tr>
<tr><td>Configurazione .env</td><td>Opzionale</td><td>APP_KEY, credenziali DB, SMTP, token OAuth</td></tr>
<tr><td>Metadati</td><td>Sempre</td><td>Versione app, PHP, Laravel</td></tr>
</tbody>
</table>
<h3>Come eseguire un backup</h3>
<table>
<thead><tr><th>Via Interfaccia Web</th><th>Via Comando</th></tr></thead>
<tbody>
<tr>
<td>
<ol>
<li>Vai su <strong>Admin Backup</strong></li>
<li>Verifica le impostazioni (includi files/.env)</li>
<li>Clicca <strong>Esegui Backup Ora</strong></li>
<li>Scarica il file ZIP generato</li>
</ol>
</td>
<td>
<pre>php artisan backup:run</pre>
<p>Il file viene creato in <code>storage/app/backups/</code></p>
</td>
</tr>
</tbody>
</table>
<h3>Backup automatico</h3>
<p>Puoi abilitare il backup automatico dalla pagina <strong>Admin Backup</strong>:</p>
<ul>
<li><strong>Frequenza:</strong> giornaliero, settimanale o mensile</li>
<li><strong>Ora:</strong> consigliata 03:00 (notte)</li>
<li><strong>Requisito:</strong> il cron di Laravel deve essere configurato sul server</li>
</ul>
<div class="callout callout-warning">
<i class="fas fa-clock mr-2"></i>
Per il backup automatico, aggiungi questo cron:
<pre>* * * * * cd {{ base_path() }} && php artisan schedule:run >> /dev/null 2>&1</pre>
</div>
<hr>
<h3>Procedura di Migrazione Completa</h3>
<p>Segui questa procedura per migrare l'applicazione su un nuovo server LAMP (Linux, Apache, MySQL, PHP).</p>
<div class="callout callout-danger">
<strong>ATTENZIONE: APP_KEY</strong>
<p>L'APP_KEY cripta i dati sensibili (password email SMTP, token OAuth Google Drive, ecc.).</p>
<ul>
<li><strong>Se hai il .env originale dal backup:</strong> NON rigenerare APP_KEY mantieni i dati criptati accessibili.</li>
<li><strong>Se hai perso il .env:</strong> DEVI rigenerare APP_KEY, ma perderai l'accesso ai dati criptati esistenti. Dovrai riconfigurare SMTP e OAuth.</li>
</ul>
</div>
<h4>Passo 1: Preparazione del server</h4>
<pre>sudo apt update && sudo apt upgrade -y</pre>
<h4>Passo 2: Installa LAMP + PHP 8.4</h4>
<pre>sudo apt install -y apache2 mariadb-server php8.4 php8.4-cli \
php8.4-mysql php8.4-xml php8.4-mbstring php8.4-curl \
php8.4-zip php8.4-bcmath php8.4-gd php8.4-intl \
composer git unzip
sudo a2enmod rewrite</pre>
<h4>Passo 3: Ottieni l'applicazione</h4>
<p>Puoi clonare il repository con git oppure estrarre il file ZIP ricevuto nella cartella <code>/var/www/glastree</code>.</p>
<pre>cd /var/www
# Opzione A — Git clone
git clone &lt;URL_REPOSITORY&gt; glastree
# Opzione B — ZIP (se non hai git)
# unzip /percorso/del/glastree.zip -d glastree
cd glastree
composer install --no-dev --optimize-autoloader
npm ci && npm run build</pre>
<h4>Passo 4: Carica il backup</h4>
<p>Copia il file <code>backup.zip</code> sul nuovo server e estrailo:</p>
<pre>mkdir -p /tmp/restore
unzip backup.zip -d /tmp/restore/
ls -la /tmp/restore/
# Dovresti vedere: database.sql .env files/ manifest.json</pre>
<h4>Passo 5: Crea database e importa</h4>
<pre>sudo mysql -u root &lt;&lt;EOF
CREATE DATABASE glastree CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'glastree'@'localhost' IDENTIFIED BY 'ScegliPasswordFortep4ss!';
GRANT ALL ON glastree.* TO 'glastree'@'localhost';
FLUSH PRIVILEGES;
EOF
mysql -u glastree -p glastree &lt; /tmp/restore/database.sql</pre>
<h4>Passo 6: Configura .env</h4>
<pre>cp /tmp/restore/.env .env
# Modifica se necessario:
# APP_URL=https://nuovo-dominio.it
# DB_HOST=localhost
# DB_DATABASE=glastree
# DB_USERNAME=glastree
# DB_PASSWORD=ScegliPasswordFortep4ss!
# DECIDI SULL'APP_KEY (leggi avviso sopra!)
# php artisan key:generate ← SOLO se non hai il .env originale</pre>
<h4>Passo 7: Ripristina file e permessi</h4>
<pre>cp -r /tmp/restore/files/* storage/app/documenti/
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 755 storage bootstrap/cache
php artisan storage:link</pre>
<h4>Passo 8: Cache</h4>
<pre>php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache</pre>
<h4>Passo 9: Configura Apache</h4>
<pre>sudo tee /etc/apache2/sites-available/glastree.conf &lt;&lt;APACHE
&lt;VirtualHost *:80&gt;
ServerName glastree.esempio.it
DocumentRoot /var/www/glastree/public
&lt;Directory /var/www/glastree/public&gt;
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
&lt;/Directory&gt;
ErrorLog \${APACHE_LOG_DIR}/glastree_error.log
CustomLog \${APACHE_LOG_DIR}/glastree_access.log combined
&lt;/VirtualHost&gt;
APACHE
sudo a2dissite 000-default.conf
sudo a2ensite glastree.conf
sudo systemctl reload apache2</pre>
<h4>Passo 10: SSL Let's Encrypt</h4>
<pre>sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d glastree.esempio.it</pre>
<h4>Passo 11: Cron per backup automatico</h4>
<pre>sudo crontab -u www-data -e
# Inserisci:
* * * * * cd /var/www/glastree && php artisan schedule:run >> /dev/null 2>&1</pre>
<h4>Passo 12: Verifica finale</h4>
<ol>
<li>Apri https://glastree.esempio.it nel browser</li>
<li>Fai login con le credenziali esistenti</li>
<li>Verifica: Dashboard, Documenti, Impostazioni, Email</li>
<li>Se hai rigenerato APP_KEY: riconfigura SMTP e OAuth Google Drive</li>
</ol>
<hr>
<h3>PHP Web Installer</h3>
<p>L'applicazione include un <strong>wizard di installazione web</strong> nella cartella <code>installer/</code>. Questo wizard gestisce sia installazioni fresh che ripristino da backup.</p>
<p>Vedi la sezione <strong>Installazione</strong> per la guida completa.</p>
<div class="page-break"></div>
{{-- 7. Docker --}}
<h2 id="docker">7. Docker</h2>
<h3>Creare un'immagine Docker con dati di base</h3>
<p>Puoi creare un'immagine Docker pronta all'uso che include l'applicazione e i dati di base (tipologie documenti, tipologie eventi, ruoli). Scegli la configurazione in base alle tue esigenze:</p>
<table>
<thead><tr><th>Configurazione</th><th>Descrizione</th></tr></thead>
<tbody>
<tr><td><strong>SQLite (Leggero)</strong></td><td>Database embedded, nessun servizio aggiuntivo. Ideale per demo, test, sviluppo locale o ambienti con poca concorrenza.</td></tr>
<tr><td><strong>MySQL (Standard)</strong></td><td>Database separato in un container MySQL. Configurazione standard, ideale per produzione, staging e ambienti con più utenti.</td></tr>
</tbody>
</table>
<h3>1. Dockerfile (comune a entrambe le modalità)</h3>
<pre>FROM php:8.4-apache
# Abilita mod_rewrite
RUN docker-php-ext-enable opcache \
&& a2enmod rewrite
# Installa estensioni PHP (sia SQLite che MySQL)
RUN docker-php-ext-install pdo_mysql pdo_sqlite bcmath gd zip
# Installa Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Configura DocumentRoot per Laravel
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
# Copia l'applicazione
COPY . /var/www/html/
# Installa dipendenze e compila asset
RUN composer install --no-dev --optimize-autoloader \
&& mkdir -p storage/app/public \
&& mkdir -p storage/framework/cache/data \
&& mkdir -p storage/framework/sessions \
&& mkdir -p storage/framework/views \
&& mkdir -p storage/logs \
&& mkdir -p bootstrap/cache \
&& chown -R www-data:www-data storage bootstrap/cache
# Entrypoint per inizializzazione
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]</pre>
<h3>2. Entrypoint Script (comune)</h3>
<p>Crea <code>docker-entrypoint.sh</code>. Lo script rileva automaticamente la configurazione in base alle variabili d'ambiente:</p>
<pre>#!/bin/bash
set -e
# Se .env non esiste, lo genera dalle variabili d'ambiente
if [ ! -f .env ]; then
cp .env.example .env
if [ "$DB_CONNECTION" = "mysql" ]; then
# Configura MySQL
sed -i "s/DB_CONNECTION=mysql/DB_CONNECTION=mysql/" .env
sed -i "s/DB_HOST=.*/DB_HOST=${DB_HOST:-mysql}/" .env
sed -i "s/DB_PORT=.*/DB_PORT=${DB_PORT:-3306}/" .env
sed -i "s/DB_DATABASE=.*/DB_DATABASE=${DB_DATABASE:-glastree}/" .env
sed -i "s/DB_USERNAME=.*/DB_USERNAME=${DB_USERNAME:-glastree}/" .env
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=${DB_PASSWORD:-secret}/" .env
# Attende che MySQL sia pronto
echo "Attendo MySQL..."
until php -r "new PDO('mysql:host=${DB_HOST:-mysql};dbname=${DB_DATABASE:-glastree}', '${DB_USERNAME:-glastree}', '${DB_PASSWORD:-secret}');" 2>/dev/null; do
sleep 2
done
echo "MySQL pronto."
else
# Configura SQLite
sed -i 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' .env
sed -i 's/DB_HOST=.*//' .env
sed -i 's/DB_PORT=.*//' .env
sed -i 's/DB_DATABASE=.*/DB_DATABASE=\/var\/www\/html\/database\/database.sqlite/' .env
sed -i 's/DB_USERNAME=.*//' .env
sed -i 's/DB_PASSWORD=.*//' .env
touch database/database.sqlite
chmod 664 database/database.sqlite
fi
# Genera APP_KEY
php artisan key:generate --force
fi
# Esegui migration e seed se il database è vuoto
if [ ! -f .db_initialized ]; then
php artisan migrate --force
php artisan db:seed --force
touch .db_initialized
fi
# Configura storage link
php artisan storage:link --force 2>/dev/null || true
exec "$@"</pre>
<pre>chmod +x docker-entrypoint.sh</pre>
<h3>3. Seed Base Dati (comune)</h3>
<p>Per includere tipologie documenti, tipologie eventi e ruoli, crea un seeder dedicato:</p>
<pre>php artisan make:seeder DockerBaseDataSeeder</pre>
<p>Contenuto di <code>database/seeders/DockerBaseDataSeeder.php</code>:</p>
<pre>&lt;?php
declare(strict_types=1);
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DockerBaseDataSeeder extends Seeder
{
public function run(): void
{
// Tipologie Documenti
$tipologieDoc = ['documento', 'avatar', 'galleria', 'statuto', 'programma', 'locandina', 'altro'];
foreach ($tipologieDoc as $ord =&gt; $nome) {
DB::table('tipologie_documenti')-&gt;updateOrInsert(
['nome' =&gt; $nome],
['descrizione' =&gt; ucfirst($nome), 'ordine' =&gt; $ord + 1, 'attiva' =&gt; true]
);
}
// Tipologie Eventi
$tipologieEventi = [
['nome' =&gt; 'catechesi', 'descrizione' =&gt; 'Catechesi'],
['nome' =&gt; 'liturgia', 'descrizione' =&gt; 'Liturgia'],
['nome' =&gt; 'animazione', 'descrizione' =&gt; 'Animazione'],
['nome' =&gt; 'formazione', 'descrizione' =&gt; 'Formazione'],
['nome' =&gt; 'incontro', 'descrizione' =&gt; 'Incontro'],
['nome' =&gt; 'riunione', 'descrizione' =&gt; 'Riunione'],
['nome' =&gt; 'festa', 'descrizione' =&gt; 'Festa'],
['nome' =&gt; 'altro', 'descrizione' =&gt; 'Altro'],
];
foreach ($tipologieEventi as $ord =&gt; $t) {
DB::table('tipologie_eventi')-&gt;updateOrInsert(
['nome' =&gt; $t['nome']],
['descrizione' =&gt; $t['descrizione'], 'ordine' =&gt; $ord + 1, 'attiva' =&gt; true]
);
}
// Ruoli
$ruoli = ['presidente', 'vicepresidente', 'segretario', 'tesoriere', 'consigliere', 'volontario', 'socio', 'altro'];
foreach ($ruoli as $ord =&gt; $nome) {
DB::table('ruoli')-&gt;updateOrInsert(
['nome' =&gt; $nome],
['descrizione' =&gt; ucfirst($nome), 'ordine' =&gt; $ord + 1, 'attiva' =&gt; true]
);
}
}
}</pre>
<p>Registralo in <code>database/seeders/DatabaseSeeder.php</code>:</p>
<pre>$this-&gt;call([
// ... altri seeder ...
DockerBaseDataSeeder::class,
]);</pre>
<h3>4. Docker Compose</h3>
<h4>Opzione A — SQLite (leggero)</h4>
<pre>services:
glastree:
build: .
ports:
- "8080:80"
volumes:
- glastree_storage:/var/www/html/storage
- glastree_db:/var/www/html/database
environment:
- APP_ENV=production
- APP_DEBUG=false
- APP_URL=http://localhost:8080
- DB_CONNECTION=sqlite
volumes:
glastree_storage:
glastree_db:</pre>
<h4>Opzione B — MySQL (standard)</h4>
<pre>services:
glastree:
build: .
ports:
- "8080:80"
volumes:
- glastree_storage:/var/www/html/storage
depends_on:
mysql:
condition: service_healthy
environment:
- APP_ENV=production
- APP_DEBUG=false
- APP_URL=http://localhost:8080
- DB_CONNECTION=mysql
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=glastree
- DB_USERNAME=glastree
- DB_PASSWORD=secret
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
environment:
- MYSQL_DATABASE=glastree
- MYSQL_USER=glastree
- MYSQL_PASSWORD=secret
- MYSQL_ROOT_PASSWORD=root_secret
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
volumes:
glastree_storage:
mysql_data:</pre>
<h3>5. Build e avvio</h3>
<p>Assicurati di essere nella cartella del progetto (ottenuta via <code>git clone</code> o estraendo il ZIP), poi:</p>
<pre># Costruisci l'immagine (comune)
docker build -t glastree:latest .
# Avvia con SQLite
docker compose -f docker-compose.yml up -d
# Oppure avvia con MySQL
# docker compose -f docker-compose.mysql.yml up -d
# Apri nel browser
open http://localhost:8080</pre>
<h3>6. Primo accesso</h3>
<p>Al primo avvio, l'entrypoint esegue automaticamente migration e seed. Troverai già popolati:</p>
<ul>
<li><strong>Tipologie Documenti:</strong> documento, avatar, galleria, statuto, programma, locandina, altro</li>
<li><strong>Tipologie Eventi:</strong> catechesi, liturgia, animazione, formazione, incontro, riunione, festa, altro</li>
<li><strong>Ruoli:</strong> presidente, vicepresidente, segretario, tesoriere, consigliere, volontario, socio, altro</li>
</ul>
<p>Dovrai creare il primo utente amministratore via CLI:</p>
<pre>docker exec -it glastree php artisan tinker --execute="
\$u = new \App\Models\User;
\$u->name = 'Amministratore';
\$u->email = 'admin@esempio.it';
\$u->password = bcrypt('password-forte');
\$u->is_admin = true;
\$u->permissions = ['individui' => 2, 'gruppi' => 2, 'eventi' => 2, 'documenti' => 2, 'mailing' => 2, 'viste' => 2, 'report' => 2, 'settings' => 2];
\$u->save();
echo 'Utente creato con successo!';
"</pre>
<div class="callout callout-warning">
<h4>Note importanti</h4>
<ul>
<li><strong>Persistenza:</strong> i volumi Docker preservano storage e database tra i riavvii</li>
<li><strong>APP_KEY:</strong> viene generata al primo avvio — se elimini i volumi, la perdi e i dati criptati diventano inaccessibili</li>
<li><strong>MySQL in produzione:</strong> imposta password sicure per <code>MYSQL_PASSWORD</code> e <code>MYSQL_ROOT_PASSWORD</code></li>
<li><strong>File compose separati:</strong> puoi mantenere entrambi i file (<code>docker-compose.yml</code> per SQLite, <code>docker-compose.mysql.yml</code> per MySQL) e scegliere con <code>-f</code></li>
</ul>
</div>
</body>
</html>
+14
View File
@@ -92,6 +92,20 @@
<a href="/mailing-liste" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
@if(Auth::user()->canManage('settings'))
<div class="col-lg-3 col-6">
<div class="small-box bg-navy">
<div class="inner">
<h3>{{ $stats['backups'] }}</h3>
<p>Backup</p>
</div>
<div class="icon">
<i class="fas fa-hdd"></i>
</div>
<a href="{{ route('admin.backup.index') }}" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
@endif
</div>
<div class="row">
+35 -15
View File
@@ -85,12 +85,6 @@ $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
<p>Dashboard</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('help') }}" class="nav-link {{ request()->routeIs('help') ? 'active' : '' }}">
<i class="nav-icon fas fa-question-circle"></i>
<p>Guida</p>
</a>
</li>
@if(Auth::user()->canAccess('individui'))
<li class="nav-item">
<a href="{{ route('individui.index') }}" class="nav-link">
@@ -220,19 +214,45 @@ $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
<p>Impostazioni</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/utenti" class="nav-link {{ request()->is('admin*') ? 'active' : '' }}">
<li class="nav-item has-treeview {{ request()->is('admin/*') ? 'menu-open' : '' }}">
<a href="#" class="nav-link {{ request()->is('admin/*') ? 'active' : '' }}">
<i class="nav-icon fas fa-users-cog"></i>
<p>Admin</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/backup" class="nav-link {{ request()->is('admin/backup*') ? 'active' : '' }}">
<i class="nav-icon fas fa-hdd"></i>
<p>Backup</p>
<p>Admin <i class="right fas fa-angle-left" style="font-size: 0.8rem;"></i></p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="/admin/utenti" class="nav-link {{ request()->routeIs('admin.utenti.*') ? 'active' : '' }}">
<i class="nav-icon fas fa-user-shield"></i>
<p>Utenti</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.ruoli.index') }}" class="nav-link {{ request()->routeIs('admin.ruoli.*') ? 'active' : '' }}">
<i class="nav-icon fas fa-tag"></i>
<p>Ruoli</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/backup" class="nav-link {{ request()->routeIs('admin.backup.*') ? 'active' : '' }}">
<i class="nav-icon fas fa-hdd"></i>
<p>Backup</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.activity-logs.index') }}" class="nav-link {{ request()->routeIs('admin.activity-logs.*') ? 'active' : '' }}">
<i class="nav-icon fas fa-history"></i>
<p>Log Attività</p>
</a>
</li>
</ul>
</li>
@endif
<li class="nav-item">
<a href="{{ route('help') }}" class="nav-link {{ request()->routeIs('help') ? 'active' : '' }}">
<i class="nav-icon fas fa-question-circle"></i>
<p>Guida / Aiuto</p>
</a>
</li>
</ul>
</nav>
</div>