feat(modifiche): compact changelog page with update action and commit list

This commit is contained in:
michele 2026-03-12 00:08:21 +00:00
parent 90cb8514ff
commit 334cc754f9
3 changed files with 260 additions and 20 deletions

View File

@ -3,8 +3,13 @@
namespace App\Filament\Pages\Supporto;
use BackedEnum;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process;
use Throwable;
use UnitEnum;
class Modifiche extends Page
@ -23,6 +28,30 @@ class Modifiche extends Page
protected string $view = 'filament.pages.supporto.modifiche';
public string $currentVersion = '0.0.0';
public string $versionSource = 'config';
public string $updateChannel = 'free';
public bool $updateForce = false;
public bool $updateDryRun = false;
public ?int $lastUpdateExitCode = null;
public ?string $lastUpdateOutput = null;
public ?string $lastUpdateAt = null;
/** @var array<int,array{hash:string,short_hash:string,author:string,date:string,message:string}> */
public array $latestCommits = [];
public function mount(): void
{
$this->reloadDashboardData();
}
public static function canAccess(): bool
{
$user = Auth::user();
@ -33,4 +62,117 @@ public static function canAccess(): bool
// Pagina informativa: accessibile a chi accede al pannello.
return true;
}
public function canRunUpdate(): bool
{
$user = Auth::user();
return $user && method_exists($user, 'hasAnyRole')
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore']);
}
public function reloadDashboardData(): void
{
$this->loadVersionInfo();
$this->loadLatestCommits();
}
public function runUpdate(): void
{
if (! $this->canRunUpdate()) {
Notification::make()
->title('Permessi insufficienti')
->body('Solo super-admin/admin/amministratore possono lanciare gli aggiornamenti.')
->danger()
->send();
return;
}
$params = [
'--channel' => $this->updateChannel,
'--apply' => true,
];
if ($this->updateDryRun) {
$params['--dry-run'] = true;
}
if ($this->updateForce) {
$params['--force'] = true;
}
try {
$exitCode = Artisan::call('netgescon:update', $params);
$output = trim((string) Artisan::output());
$this->lastUpdateExitCode = $exitCode;
$this->lastUpdateOutput = $output;
$this->lastUpdateAt = now()->format('d/m/Y H:i:s');
$this->reloadDashboardData();
Notification::make()
->title($exitCode === 0 ? 'Aggiornamento completato' : 'Aggiornamento terminato con errori')
->body($exitCode === 0 ? 'Procedura conclusa. Versione e commit aggiornati.' : 'Controlla il log tecnico nella sezione output.')
->{$exitCode === 0 ? 'success' : 'danger'}()
->send();
} catch (Throwable $e) {
$this->lastUpdateExitCode = 1;
$this->lastUpdateAt = now()->format('d/m/Y H:i:s');
$this->lastUpdateOutput = 'Errore durante esecuzione update: ' . $e->getMessage();
Notification::make()
->title('Errore durante update')
->body($e->getMessage())
->danger()
->send();
}
}
private function loadVersionInfo(): void
{
$versionFile = base_path('VERSION');
if (File::exists($versionFile)) {
$fileVersion = trim((string) File::get($versionFile));
if ($fileVersion !== '') {
$this->currentVersion = $fileVersion;
$this->versionSource = 'VERSION';
return;
}
}
$this->currentVersion = (string) config('netgescon.version', '0.0.0');
$this->versionSource = 'config';
}
private function loadLatestCommits(): void
{
$this->latestCommits = [];
$format = '%H|%h|%an|%ad|%s';
$command = 'git log -n 20 --date=format:"%d/%m/%Y %H:%M" --pretty=format:"' . $format . '"';
$result = Process::path(base_path())->run($command);
if (! $result->successful()) {
return;
}
$lines = preg_split('/\r\n|\r|\n/', trim($result->output())) ?: [];
foreach ($lines as $line) {
$parts = explode('|', $line, 5);
if (count($parts) !== 5) {
continue;
}
$this->latestCommits[] = [
'hash' => (string) $parts[0],
'short_hash' => (string) $parts[1],
'author' => (string) $parts[2],
'date' => (string) $parts[3],
'message' => (string) $parts[4],
];
}
}
}

View File

@ -23,3 +23,4 @@ # Modifiche NetGescon
| 11/03/2026 | 0.8.x-dev | CTI Panasonic / Sync staging->sviluppo | Introdotti mirror payload CTI opzionale (solo staging) e comando `cti:sync-postit-from-staging` per riallineamento unidirezionale record `chiamate_post_it` Panasonic verso sviluppo | [U] |
| 11/03/2026 | 0.8.x-dev | Ticket Mobile / Allegati | Fix accesso allegati con route protetta `admin.tickets.attachments.view` (stop 403 da URL `/storage`), anteprima in modal e lista allegati stile catalogo con descrizione per file in inserimento mobile (foto + documenti) | [U] |
| 11/03/2026 | 0.8.x-dev | CTI Panasonic / Tracciamento | Aggiunto `trace_id` in risposta incoming/call-ended e log strutturato (`CTI Panasonic trace`) per audit chiamate reali/test, ricerca eventi mancanti e diagnosi `event_id`/telefono/canale mirror | [U] |
| 12/03/2026 | 0.8.x-dev | Supporto / Modifiche / Update | Pagina `admin-filament/supporto/modifiche` resa compatta (font piccolo), aggiunta tabella ultimi commit con descrizione e pulsante per eseguire `netgescon:update --apply` con esito/output e versione corrente visibile | [U] |

View File

@ -1,5 +1,5 @@
<x-filament-panels::page>
<div class="mx-auto max-w-7xl space-y-4">
<div class="mx-auto max-w-7xl space-y-3 text-xs">
<x-filament.components.page-breadcrumbs
:breadcrumbs="[
['label' => 'Supporto', 'url' => null],
@ -7,22 +7,118 @@
]"
/>
<x-filament::section>
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<div class="text-lg font-semibold">NetGescon</div>
<div class="text-sm text-gray-600">Versione: <span class="font-mono">{{ config('netgescon.version', '0.0.0') }}</span></div>
</div>
<div class="text-sm text-gray-600">
<div>Legenda:</div>
<div class="flex flex-wrap gap-3">
<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-success-500"></span>U = utenti finali</span>
<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-primary-500"></span>P = assistenza / sviluppatori</span>
<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-warning-500"></span>E = errori non bloccanti</span>
<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-danger-500"></span>E! = errori bloccanti</span>
<x-filament::section class="!p-4">
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="space-y-1">
<div class="text-base font-semibold">NetGescon - Modifiche e Aggiornamenti</div>
<div class="text-xs text-gray-600">
Versione corrente: <span class="font-mono font-semibold">{{ $this->currentVersion }}</span>
<span class="ml-2 rounded bg-gray-100 px-1.5 py-0.5 text-[10px] uppercase">fonte {{ $this->versionSource }}</span>
</div>
</div>
<div class="space-y-1 text-xs text-gray-600">
<div class="font-medium text-gray-700">Legenda rapida</div>
<div class="flex flex-wrap gap-3">
<span class="inline-flex items-center gap-2"><span class="inline-block h-2.5 w-2.5 rounded-sm bg-success-500"></span>U utenti finali</span>
<span class="inline-flex items-center gap-2"><span class="inline-block h-2.5 w-2.5 rounded-sm bg-primary-500"></span>P assistenza/sviluppo</span>
<span class="inline-flex items-center gap-2"><span class="inline-block h-2.5 w-2.5 rounded-sm bg-warning-500"></span>E non bloccante</span>
<span class="inline-flex items-center gap-2"><span class="inline-block h-2.5 w-2.5 rounded-sm bg-danger-500"></span>E! bloccante</span>
</div>
</div>
</div>
</x-filament::section>
<x-filament::section class="!p-4">
<div class="grid gap-3 lg:grid-cols-3">
<div class="rounded-lg border bg-gray-50 p-3 lg:col-span-2">
<div class="mb-2 text-xs font-semibold text-gray-700">Aggiornamento da server distribution</div>
<div class="grid gap-2 sm:grid-cols-3">
<label class="block">
<span class="mb-1 block text-[11px] font-medium">Canale</span>
<select wire:model="updateChannel" class="w-full rounded-md border-gray-300 text-xs">
<option value="free">free</option>
<option value="licensed">licensed</option>
</select>
</label>
<label class="inline-flex items-center gap-2 rounded-md border bg-white px-2 py-1.5 text-[11px]">
<input type="checkbox" wire:model="updateDryRun" class="rounded border-gray-300" />
Dry run
</label>
<label class="inline-flex items-center gap-2 rounded-md border bg-white px-2 py-1.5 text-[11px]">
<input type="checkbox" wire:model="updateForce" class="rounded border-gray-300" />
Force
</label>
</div>
<div class="mt-3 flex flex-wrap gap-2">
<x-filament::button size="sm" wire:click="runUpdate" :disabled="!$this->canRunUpdate()">
Lancia aggiornamento
</x-filament::button>
<x-filament::button size="sm" color="gray" wire:click="reloadDashboardData">
Aggiorna dati pagina
</x-filament::button>
</div>
@if(! $this->canRunUpdate())
<div class="mt-2 text-[11px] text-amber-700">Permesso update non disponibile per il tuo ruolo.</div>
@endif
</div>
<div class="rounded-lg border bg-white p-3">
<div class="text-xs font-semibold text-gray-700">Esito ultimo update</div>
<div class="mt-2 space-y-1 text-[11px] text-gray-600">
<div>Ultima esecuzione: <span class="font-medium">{{ $this->lastUpdateAt ?? '-' }}</span></div>
<div>
Exit code:
@if($this->lastUpdateExitCode === null)
<span class="font-medium">-</span>
@elseif($this->lastUpdateExitCode === 0)
<span class="rounded bg-emerald-100 px-1.5 py-0.5 font-semibold text-emerald-800">0 OK</span>
@else
<span class="rounded bg-rose-100 px-1.5 py-0.5 font-semibold text-rose-800">{{ $this->lastUpdateExitCode }} ERRORE</span>
@endif
</div>
</div>
</div>
</div>
@if(filled($this->lastUpdateOutput))
<div class="mt-3 rounded-lg border bg-slate-950 p-3">
<div class="mb-1 text-[11px] font-semibold text-slate-200">Output tecnico</div>
<pre class="max-h-64 overflow-auto whitespace-pre-wrap text-[11px] leading-relaxed text-slate-100">{{ $this->lastUpdateOutput }}</pre>
</div>
@endif
</x-filament::section>
<x-filament::section class="!p-4">
<div class="mb-2 text-xs font-semibold text-gray-700">Ultimi commit (descrizioni)</div>
<div class="overflow-x-auto">
<table class="min-w-full border-collapse border text-[11px]">
<thead>
<tr class="bg-slate-100 text-slate-700">
<th class="border px-2 py-1.5 text-left">Data</th>
<th class="border px-2 py-1.5 text-left">Commit</th>
<th class="border px-2 py-1.5 text-left">Autore</th>
<th class="border px-2 py-1.5 text-left">Descrizione</th>
</tr>
</thead>
<tbody>
@forelse($this->latestCommits as $commit)
<tr class="hover:bg-slate-50">
<td class="border px-2 py-1.5 whitespace-nowrap">{{ $commit['date'] }}</td>
<td class="border px-2 py-1.5 font-mono">{{ $commit['short_hash'] }}</td>
<td class="border px-2 py-1.5">{{ $commit['author'] }}</td>
<td class="border px-2 py-1.5">{{ $commit['message'] }}</td>
</tr>
@empty
<tr>
<td colspan="4" class="border px-2 py-3 text-center text-gray-500">Nessun commit disponibile.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</x-filament::section>
@ -48,15 +144,16 @@
$gitRawHtml = str_replace(array_keys($replacements), array_values($replacements), $gitRawHtml);
@endphp
<x-filament::section>
<div class="prose max-w-none">
<x-filament::section class="!p-4">
<div class="mb-2 text-xs font-semibold text-gray-700">Registro funzionale (manuale)</div>
<div class="prose prose-sm max-w-none text-[11px] leading-relaxed">
{!! $rawHtml !!}
</div>
</x-filament::section>
<x-filament::section>
<div class="mb-2 text-sm font-semibold text-gray-700">Changelog Automatico da Git</div>
<div class="prose max-w-none">
<x-filament::section class="!p-4">
<div class="mb-2 text-xs font-semibold text-gray-700">Changelog automatico da script</div>
<div class="prose prose-sm max-w-none text-[11px] leading-relaxed">
{!! $gitRawHtml !!}
</div>
</x-filament::section>