feat(modifiche): compact changelog page with update action and commit list
This commit is contained in:
parent
90cb8514ff
commit
334cc754f9
|
|
@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] |
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user