Supporto Modifiche: aggiunge sync staging da Gitea
This commit is contained in:
parent
cc1c8f69aa
commit
8ee59acb31
295
app/Console/Commands/NetgesconDevelopmentSnapshotCommand.php
Normal file
295
app/Console/Commands/NetgesconDevelopmentSnapshotCommand.php
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
|
||||||
|
class NetgesconDevelopmentSnapshotCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'netgescon:development-snapshot
|
||||||
|
{--output= : Output markdown custom}
|
||||||
|
{--json= : Output JSON custom}';
|
||||||
|
|
||||||
|
protected $description = 'Rigenera una snapshot locale di continuita sviluppo per Supporto > Modifiche';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$generatedDir = storage_path('app/support/generated');
|
||||||
|
File::ensureDirectoryExists($generatedDir);
|
||||||
|
|
||||||
|
$outputPath = trim((string) $this->option('output')) !== ''
|
||||||
|
? trim((string) $this->option('output'))
|
||||||
|
: $generatedDir . '/CONTINUITA-SVILUPPO-SNAPSHOT.md';
|
||||||
|
|
||||||
|
$jsonPath = trim((string) $this->option('json')) !== ''
|
||||||
|
? trim((string) $this->option('json'))
|
||||||
|
: $generatedDir . '/CONTINUITA-SVILUPPO-SNAPSHOT.json';
|
||||||
|
|
||||||
|
$manualPath = base_path('docs/support/MANUALE-SVILUPPO-OPERATIVO.md');
|
||||||
|
$bugPath = base_path('docs/support/BUG-RISOLTI-E-VALIDAZIONI.md');
|
||||||
|
$continuityPath = base_path('docs/support/CONTINUITA-SVILUPPO-AGENT.md');
|
||||||
|
$pbxPath = base_path('docs/support/PBX-PANASONIC-STATO-OPERATIVO.md');
|
||||||
|
|
||||||
|
$manual = is_file($manualPath) ? (string) @file_get_contents($manualPath) : '';
|
||||||
|
$bugs = is_file($bugPath) ? (string) @file_get_contents($bugPath) : '';
|
||||||
|
$continuity = is_file($continuityPath) ? (string) @file_get_contents($continuityPath) : '';
|
||||||
|
$pbx = is_file($pbxPath) ? (string) @file_get_contents($pbxPath) : '';
|
||||||
|
|
||||||
|
$branch = $this->gitValue(['rev-parse', '--abbrev-ref', 'HEAD']) ?? '-';
|
||||||
|
$commit = $this->gitValue(['rev-parse', '--short', 'HEAD']) ?? '-';
|
||||||
|
|
||||||
|
$recentCommits = $this->recentCommits();
|
||||||
|
$recentPages = $this->recentPages();
|
||||||
|
$commitNotes = $this->recentCommitNotes();
|
||||||
|
|
||||||
|
$snapshot = [];
|
||||||
|
$snapshot[] = '# Snapshot continuita sviluppo';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = 'Generata automaticamente il ' . now()->format('d/m/Y H:i:s') . '.';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Stato Git corrente';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '- Branch: `' . $branch . '`';
|
||||||
|
$snapshot[] = '- Commit: `' . $commit . '`';
|
||||||
|
$snapshot[] = '- Workspace: `' . base_path() . '`';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Focus operativo';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = $this->normalizeSectionLines($this->extractSection($manual, '## Implementazioni recenti da tenere come riferimento'));
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Stato PBX Panasonic';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = $this->normalizeSectionLines($this->extractSection($pbx, '## Stato attuale'));
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Validazioni ancora aperte';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = $this->normalizeSectionLines($this->extractSection($bugs, '## Validazioni ancora aperte'));
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Prossimi passi consigliati';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = $this->normalizeSectionLines($this->extractSection($manual, '## Prossimi passi consigliati'));
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Regole di continuita';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = $this->normalizeSectionLines($this->extractSection($continuity, '## Regola semplice'));
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Ultimi commit';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '| Data | Commit | Messaggio |';
|
||||||
|
$snapshot[] = '| --- | --- | --- |';
|
||||||
|
foreach ($recentCommits as $row) {
|
||||||
|
$snapshot[] = '| ' . $row['date'] . ' | `' . $row['hash'] . '` | ' . str_replace('|', '-', $row['message']) . ' |';
|
||||||
|
}
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Note commit recenti';
|
||||||
|
$snapshot[] = '';
|
||||||
|
if ($commitNotes === []) {
|
||||||
|
$snapshot[] = '- Nessuna nota commit strutturata disponibile tra gli ultimi commit.';
|
||||||
|
} else {
|
||||||
|
foreach ($commitNotes as $row) {
|
||||||
|
$snapshot[] = '- `' . $row['hash'] . '` ' . $row['message'];
|
||||||
|
$snapshot[] = ' - Nota: ' . $row['note'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Pagine toccate di recente';
|
||||||
|
$snapshot[] = '';
|
||||||
|
if ($recentPages === []) {
|
||||||
|
$snapshot[] = '- Nessuna pagina recente rilevata.';
|
||||||
|
} else {
|
||||||
|
foreach ($recentPages as $row) {
|
||||||
|
$snapshot[] = '- `' . $row['commit'] . '` ' . $row['page'] . ' -> `' . $row['path'] . '`';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '## Fonti da leggere per ripartire';
|
||||||
|
$snapshot[] = '';
|
||||||
|
$snapshot[] = '- `docs/support/MANUALE-SVILUPPO-OPERATIVO.md`';
|
||||||
|
$snapshot[] = '- `docs/support/PBX-PANASONIC-STATO-OPERATIVO.md`';
|
||||||
|
$snapshot[] = '- `docs/support/BUG-RISOLTI-E-VALIDAZIONI.md`';
|
||||||
|
$snapshot[] = '- `docs/support/CONTINUITA-SVILUPPO-AGENT.md`';
|
||||||
|
|
||||||
|
File::put($outputPath, implode(PHP_EOL, $snapshot) . PHP_EOL);
|
||||||
|
File::put($jsonPath, json_encode([
|
||||||
|
'generated_at' => now()->toIso8601String(),
|
||||||
|
'branch' => $branch,
|
||||||
|
'commit' => $commit,
|
||||||
|
'output_path' => $outputPath,
|
||||||
|
'source_files' => [
|
||||||
|
'docs/support/MANUALE-SVILUPPO-OPERATIVO.md',
|
||||||
|
'docs/support/PBX-PANASONIC-STATO-OPERATIVO.md',
|
||||||
|
'docs/support/BUG-RISOLTI-E-VALIDAZIONI.md',
|
||||||
|
'docs/support/CONTINUITA-SVILUPPO-AGENT.md',
|
||||||
|
],
|
||||||
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
|
||||||
|
$this->info('Snapshot continuita sviluppo aggiornata.');
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function gitValue(array $arguments): ?string
|
||||||
|
{
|
||||||
|
$result = Process::path(base_path())
|
||||||
|
->timeout(120)
|
||||||
|
->run(array_merge(['git'], $arguments));
|
||||||
|
|
||||||
|
if (! $result->successful()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = trim((string) $result->output());
|
||||||
|
|
||||||
|
return $output !== '' ? $output : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<int,array{hash:string,date:string,message:string}> */
|
||||||
|
private function recentCommits(): array
|
||||||
|
{
|
||||||
|
$rows = [];
|
||||||
|
$result = Process::path(base_path())->timeout(120)->run([
|
||||||
|
'git', 'log', '-n', '8', '--date=format:%d/%m/%Y %H:%M', '--pretty=format:%h|%ad|%s',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $result->successful()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = preg_split('/\r\n|\r|\n/', trim((string) $result->output())) ?: [];
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$parts = explode('|', $line, 3);
|
||||||
|
if (count($parts) !== 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows[] = [
|
||||||
|
'hash' => (string) $parts[0],
|
||||||
|
'date' => (string) $parts[1],
|
||||||
|
'message' => (string) $parts[2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<int,array{page:string,path:string,commit:string}> */
|
||||||
|
private function recentPages(): array
|
||||||
|
{
|
||||||
|
$rows = [];
|
||||||
|
$seen = [];
|
||||||
|
$result = Process::path(base_path())->timeout(120)->run([
|
||||||
|
'git', 'log', '-n', '12', '--name-only', '--pretty=format:@@@%h', '--', 'app/Filament/Pages', 'resources/views/filament/pages',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $result->successful()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentCommit = null;
|
||||||
|
$lines = preg_split('/\r\n|\r|\n/', (string) $result->output()) ?: [];
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$line = trim((string) $line);
|
||||||
|
if ($line === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($line, '@@@')) {
|
||||||
|
$currentCommit = substr($line, 3);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($currentCommit === null || isset($seen[$line])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! str_starts_with($line, 'app/Filament/Pages/') && ! str_starts_with($line, 'resources/views/filament/pages/')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$seen[$line] = true;
|
||||||
|
$rows[] = [
|
||||||
|
'page' => basename($line),
|
||||||
|
'path' => $line,
|
||||||
|
'commit' => $currentCommit,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (count($rows) >= 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<int,array{hash:string,message:string,note:string}> */
|
||||||
|
private function recentCommitNotes(): array
|
||||||
|
{
|
||||||
|
$path = storage_path('app/support/commit-notes.json');
|
||||||
|
if (! is_file($path)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = json_decode((string) @file_get_contents($path), true);
|
||||||
|
if (! is_array($decoded)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
foreach ($this->recentCommits() as $commit) {
|
||||||
|
$fullHash = $this->gitValue(['rev-parse', $commit['hash']]);
|
||||||
|
if (! is_string($fullHash) || ! isset($decoded[$fullHash]) || ! is_string($decoded[$fullHash])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows[] = [
|
||||||
|
'hash' => $commit['hash'],
|
||||||
|
'message' => $commit['message'],
|
||||||
|
'note' => trim($decoded[$fullHash]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_slice($rows, 0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractSection(string $markdown, string $heading): string
|
||||||
|
{
|
||||||
|
if ($markdown === '' || ! str_contains($markdown, $heading)) {
|
||||||
|
return '- Sezione non disponibile.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = strpos($markdown, $heading);
|
||||||
|
if ($offset === false) {
|
||||||
|
return '- Sezione non disponibile.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$slice = substr($markdown, $offset + strlen($heading));
|
||||||
|
if (! is_string($slice)) {
|
||||||
|
return '- Sezione non disponibile.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/\n##\s+/', $slice, $match, PREG_OFFSET_CAPTURE) === 1) {
|
||||||
|
$slice = substr($slice, 0, (int) $match[0][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim($slice) !== '' ? trim($slice) : '- Sezione non disponibile.';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeSectionLines(string $text): string
|
||||||
|
{
|
||||||
|
$lines = preg_split('/\r\n|\r|\n/', trim($text)) ?: [];
|
||||||
|
$cleaned = [];
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$line = trim((string) $line);
|
||||||
|
if ($line === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cleaned[] = $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cleaned === [] ? '- Nessuna informazione disponibile.' : implode(PHP_EOL, $cleaned);
|
||||||
|
}
|
||||||
|
}
|
||||||
262
app/Console/Commands/NetgesconGitSyncCommand.php
Normal file
262
app/Console/Commands/NetgesconGitSyncCommand.php
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
|
||||||
|
class NetgesconGitSyncCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'netgescon:git-sync
|
||||||
|
{--remote=origin : Remote Git da usare}
|
||||||
|
{--branch=main : Branch da allineare}
|
||||||
|
{--force : Continua anche con working tree sporco}
|
||||||
|
{--progress-file= : File JSON dove scrivere lo stato avanzamento}';
|
||||||
|
|
||||||
|
protected $description = 'Allinea il nodo corrente da Gitea/Git e rigenera il materiale automatico per Supporto > Modifiche';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$remote = trim((string) $this->option('remote')) ?: 'origin';
|
||||||
|
$branch = trim((string) $this->option('branch')) ?: 'main';
|
||||||
|
$force = (bool) $this->option('force');
|
||||||
|
|
||||||
|
if (! is_dir(base_path('.git'))) {
|
||||||
|
$this->error('Repository Git non trovato nella base path corrente.');
|
||||||
|
$this->writeProgress(100, 'Repository Git non trovato', 'failed', 1);
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
'message' => 'Repository Git non trovato',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeProgress(5, 'Preparazione sync da Gitea', 'running');
|
||||||
|
|
||||||
|
$currentBranch = $this->runGit(['rev-parse', '--abbrev-ref', 'HEAD']);
|
||||||
|
$currentCommit = $this->runGit(['rev-parse', '--short', 'HEAD']);
|
||||||
|
|
||||||
|
if ($currentBranch === null || $currentCommit === null) {
|
||||||
|
$this->error('Impossibile leggere lo stato Git locale.');
|
||||||
|
$this->writeProgress(100, 'Impossibile leggere stato Git locale', 'failed', 1);
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
'message' => 'Impossibile leggere stato Git locale',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dirtyResult = Process::path(base_path())->run(['git', 'status', '--porcelain']);
|
||||||
|
$isDirty = trim((string) $dirtyResult->output()) !== '';
|
||||||
|
|
||||||
|
if ($isDirty && ! $force) {
|
||||||
|
$this->error('Working tree sporco: sync bloccata per evitare conflitti locali.');
|
||||||
|
$this->writeProgress(100, 'Working tree sporco: sync bloccata', 'failed', 1);
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
'message' => 'Working tree sporco: sync bloccata',
|
||||||
|
'remote' => $remote,
|
||||||
|
'branch' => $branch,
|
||||||
|
'before_commit' => $currentCommit,
|
||||||
|
'working_tree_dirty' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("Fetch da {$remote}...");
|
||||||
|
$this->writeProgress(18, 'Fetch remoto da Gitea', 'running');
|
||||||
|
|
||||||
|
$fetch = Process::path(base_path())
|
||||||
|
->timeout(1800)
|
||||||
|
->run(['git', 'fetch', $remote]);
|
||||||
|
|
||||||
|
if (! $fetch->successful()) {
|
||||||
|
$this->line(trim((string) ($fetch->errorOutput() !== '' ? $fetch->errorOutput() : $fetch->output())));
|
||||||
|
$this->writeProgress(100, 'Fetch remoto fallito', 'failed', 1);
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
'message' => 'Fetch remoto fallito',
|
||||||
|
'remote' => $remote,
|
||||||
|
'branch' => $branch,
|
||||||
|
'before_commit' => $currentCommit,
|
||||||
|
'working_tree_dirty' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeProgress(32, 'Allineamento branch locale', 'running');
|
||||||
|
|
||||||
|
if ($currentBranch !== $branch) {
|
||||||
|
$checkout = Process::path(base_path())
|
||||||
|
->timeout(1800)
|
||||||
|
->run(['git', 'checkout', $branch]);
|
||||||
|
|
||||||
|
if (! $checkout->successful()) {
|
||||||
|
$this->line(trim((string) ($checkout->errorOutput() !== '' ? $checkout->errorOutput() : $checkout->output())));
|
||||||
|
$this->writeProgress(100, 'Checkout branch fallito', 'failed', 1);
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
'message' => 'Checkout branch fallito',
|
||||||
|
'remote' => $remote,
|
||||||
|
'branch' => $branch,
|
||||||
|
'before_commit' => $currentCommit,
|
||||||
|
'working_tree_dirty' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("Pull ff-only da {$remote}/{$branch}...");
|
||||||
|
$this->writeProgress(48, 'Pull ff-only da Gitea', 'running');
|
||||||
|
|
||||||
|
$pull = Process::path(base_path())
|
||||||
|
->timeout(1800)
|
||||||
|
->run(['git', 'pull', '--ff-only', $remote, $branch]);
|
||||||
|
|
||||||
|
if (! $pull->successful()) {
|
||||||
|
$this->line(trim((string) ($pull->errorOutput() !== '' ? $pull->errorOutput() : $pull->output())));
|
||||||
|
$this->writeProgress(100, 'Pull da Gitea fallito', 'failed', 1);
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
'message' => 'Pull da Gitea fallito',
|
||||||
|
'remote' => $remote,
|
||||||
|
'branch' => $branch,
|
||||||
|
'before_commit' => $currentCommit,
|
||||||
|
'working_tree_dirty' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeProgress(68, 'Rigenerazione changelog Git automatico', 'running');
|
||||||
|
File::ensureDirectoryExists(storage_path('app/support/generated'));
|
||||||
|
|
||||||
|
$docsSync = Process::path(base_path())
|
||||||
|
->timeout(1800)
|
||||||
|
->run([
|
||||||
|
'bash',
|
||||||
|
'scripts/ops/netgescon-sync-git-changelog.sh',
|
||||||
|
'--repo',
|
||||||
|
base_path(),
|
||||||
|
'--output',
|
||||||
|
storage_path('app/support/generated/NETGESCON-GIT-AUTOCHANGELOG.md'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $docsSync->successful()) {
|
||||||
|
$this->line(trim((string) ($docsSync->errorOutput() !== '' ? $docsSync->errorOutput() : $docsSync->output())));
|
||||||
|
$this->writeProgress(100, 'Rigenerazione changelog Git fallita', 'failed', 1);
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
'message' => 'Rigenerazione changelog Git fallita',
|
||||||
|
'remote' => $remote,
|
||||||
|
'branch' => $branch,
|
||||||
|
'before_commit' => $currentCommit,
|
||||||
|
'working_tree_dirty' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeProgress(76, 'Rigenerazione snapshot continuita sviluppo', 'running');
|
||||||
|
$snapshotExit = Artisan::call('netgescon:development-snapshot');
|
||||||
|
if ($snapshotExit !== 0) {
|
||||||
|
$this->writeProgress(100, 'Rigenerazione snapshot sviluppo fallita', 'failed', 1);
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
'message' => 'Rigenerazione snapshot sviluppo fallita',
|
||||||
|
'remote' => $remote,
|
||||||
|
'branch' => $branch,
|
||||||
|
'before_commit' => $currentCommit,
|
||||||
|
'working_tree_dirty' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeProgress(82, 'Pulizia cache applicativa', 'running');
|
||||||
|
$this->callSilently('optimize:clear');
|
||||||
|
|
||||||
|
$newCommit = $this->runGit(['rev-parse', '--short', 'HEAD']) ?? $currentCommit;
|
||||||
|
$remoteRef = $this->runGit(['rev-parse', '--short', 'refs/remotes/' . $remote . '/' . $branch]) ?? $newCommit;
|
||||||
|
|
||||||
|
$this->persistSummary([
|
||||||
|
'status' => 'completed',
|
||||||
|
'exit_code' => 0,
|
||||||
|
'message' => 'Sync Git completata',
|
||||||
|
'remote' => $remote,
|
||||||
|
'branch' => $branch,
|
||||||
|
'before_commit' => $currentCommit,
|
||||||
|
'after_commit' => $newCommit,
|
||||||
|
'remote_commit' => $remoteRef,
|
||||||
|
'working_tree_dirty' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->info('Sync Git completata con successo.');
|
||||||
|
$this->writeProgress(100, 'Sync Git completata', 'completed', 0);
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runGit(array $arguments): ?string
|
||||||
|
{
|
||||||
|
$result = Process::path(base_path())
|
||||||
|
->timeout(1800)
|
||||||
|
->run(array_merge(['git'], $arguments));
|
||||||
|
|
||||||
|
if (! $result->successful()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = trim((string) $result->output());
|
||||||
|
|
||||||
|
return $output !== '' ? $output : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeProgress(int $percent, string $message, string $status, ?int $exitCode = null): void
|
||||||
|
{
|
||||||
|
$path = trim((string) $this->option('progress-file'));
|
||||||
|
if ($path === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dir = dirname($path);
|
||||||
|
if (! is_dir($dir)) {
|
||||||
|
@mkdir($dir, 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@file_put_contents($path, json_encode([
|
||||||
|
'timestamp' => now()->toIso8601String(),
|
||||||
|
'percent' => max(0, min(100, $percent)),
|
||||||
|
'message' => $message,
|
||||||
|
'status' => $status,
|
||||||
|
'exit_code' => $exitCode,
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function persistSummary(array $payload): void
|
||||||
|
{
|
||||||
|
File::ensureDirectoryExists(storage_path('app/support/generated'));
|
||||||
|
|
||||||
|
@file_put_contents(
|
||||||
|
storage_path('app/support/generated/git-sync-last.json'),
|
||||||
|
json_encode(array_merge([
|
||||||
|
'synced_at' => now()->toIso8601String(),
|
||||||
|
], $payload), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,8 @@ class Modifiche extends Page
|
||||||
|
|
||||||
public bool $updateDryRun = false;
|
public bool $updateDryRun = false;
|
||||||
|
|
||||||
|
public bool $updateSkipBackup = false;
|
||||||
|
|
||||||
public ?int $lastUpdateExitCode = null;
|
public ?int $lastUpdateExitCode = null;
|
||||||
|
|
||||||
public ?string $lastUpdateOutput = null;
|
public ?string $lastUpdateOutput = null;
|
||||||
|
|
@ -70,7 +72,49 @@ class Modifiche extends Page
|
||||||
|
|
||||||
public string $updateProgressStatus = 'idle';
|
public string $updateProgressStatus = 'idle';
|
||||||
|
|
||||||
public string $activeTab = 'errori';
|
public string $gitRemote = 'origin';
|
||||||
|
|
||||||
|
public string $gitBranch = 'main';
|
||||||
|
|
||||||
|
public bool $gitForce = false;
|
||||||
|
|
||||||
|
public ?string $gitCurrentBranch = null;
|
||||||
|
|
||||||
|
public ?string $gitCurrentCommit = null;
|
||||||
|
|
||||||
|
public ?string $gitRemoteCommit = null;
|
||||||
|
|
||||||
|
public bool $gitWorkingTreeDirty = false;
|
||||||
|
|
||||||
|
public string $gitAheadBehind = '-';
|
||||||
|
|
||||||
|
public bool $gitSyncInProgress = false;
|
||||||
|
|
||||||
|
public ?string $gitSyncJobId = null;
|
||||||
|
|
||||||
|
public int $gitSyncProgressPercent = 0;
|
||||||
|
|
||||||
|
public string $gitSyncProgressMessage = 'In attesa';
|
||||||
|
|
||||||
|
public string $gitSyncProgressStatus = 'idle';
|
||||||
|
|
||||||
|
public ?int $lastGitSyncExitCode = null;
|
||||||
|
|
||||||
|
public ?string $lastGitSyncOutput = null;
|
||||||
|
|
||||||
|
public ?string $lastGitSyncAt = null;
|
||||||
|
|
||||||
|
public ?string $lastGitSyncIssue = null;
|
||||||
|
|
||||||
|
public ?string $lastGitDocsSyncAt = null;
|
||||||
|
|
||||||
|
public ?string $developmentSnapshotHtml = null;
|
||||||
|
|
||||||
|
public ?string $developmentSnapshotPath = null;
|
||||||
|
|
||||||
|
public ?string $developmentSnapshotGeneratedAt = null;
|
||||||
|
|
||||||
|
public string $activeTab = 'manuale';
|
||||||
|
|
||||||
public ?string $selectedCommitHash = null;
|
public ?string $selectedCommitHash = null;
|
||||||
|
|
||||||
|
|
@ -110,6 +154,9 @@ class Modifiche extends Page
|
||||||
/** @var array<int,string> */
|
/** @var array<int,string> */
|
||||||
public array $functionalHighlights = [];
|
public array $functionalHighlights = [];
|
||||||
|
|
||||||
|
/** @var array<int,array{key:string,title:string,description:string,path:string,exists:bool,html:string}> */
|
||||||
|
public array $manualSections = [];
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->reloadDashboardData();
|
$this->reloadDashboardData();
|
||||||
|
|
@ -142,6 +189,9 @@ public function reloadDashboardData(): void
|
||||||
$this->loadRuntimeErrors();
|
$this->loadRuntimeErrors();
|
||||||
$this->loadRecentFilamentPages();
|
$this->loadRecentFilamentPages();
|
||||||
$this->loadFunctionalHighlights();
|
$this->loadFunctionalHighlights();
|
||||||
|
$this->loadManualSections();
|
||||||
|
$this->loadDevelopmentSnapshot();
|
||||||
|
$this->loadGitWorkspaceStatus();
|
||||||
$this->loadLatestBackupSummary();
|
$this->loadLatestBackupSummary();
|
||||||
|
|
||||||
if ($this->selectedCommitHash === null && isset($this->latestCommits[0]['hash'])) {
|
if ($this->selectedCommitHash === null && isset($this->latestCommits[0]['hash'])) {
|
||||||
|
|
@ -183,6 +233,53 @@ public function runUpdateFallback(): void
|
||||||
$this->startUpdateJob(true);
|
$this->startUpdateJob(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function runGitSync(): void
|
||||||
|
{
|
||||||
|
if (! $this->canRunUpdate()) {
|
||||||
|
Notification::make()
|
||||||
|
->title('Permessi insufficienti')
|
||||||
|
->body('Solo super-admin/admin/amministratore possono lanciare la sync da Gitea.')
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->startGitSyncJob();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateDevelopmentSnapshot(): void
|
||||||
|
{
|
||||||
|
if (! $this->canRunUpdate()) {
|
||||||
|
Notification::make()
|
||||||
|
->title('Permessi insufficienti')
|
||||||
|
->body('Solo super-admin/admin/amministratore possono rigenerare la snapshot di sviluppo.')
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exitCode = Artisan::call('netgescon:development-snapshot');
|
||||||
|
if ($exitCode !== 0) {
|
||||||
|
Notification::make()
|
||||||
|
->title('Errore snapshot sviluppo')
|
||||||
|
->body(trim((string) Artisan::output()) ?: 'Rigenerazione non riuscita.')
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reloadDashboardData();
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('Snapshot sviluppo aggiornata')
|
||||||
|
->body('Il riepilogo di continuita e stato rigenerato per questa macchina.')
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
|
||||||
public function refreshUpdateProgress(): void
|
public function refreshUpdateProgress(): void
|
||||||
{
|
{
|
||||||
$jobId = trim((string) ($this->updateJobId ?? ''));
|
$jobId = trim((string) ($this->updateJobId ?? ''));
|
||||||
|
|
@ -221,6 +318,53 @@ public function refreshUpdateProgress(): void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function refreshGitSyncProgress(): void
|
||||||
|
{
|
||||||
|
$jobId = trim((string) ($this->gitSyncJobId ?? ''));
|
||||||
|
if ($jobId === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$progressPath = storage_path('app/support/git-sync-jobs/' . $jobId . '.progress.json');
|
||||||
|
$logPath = storage_path('app/support/git-sync-jobs/' . $jobId . '.log');
|
||||||
|
|
||||||
|
if (! is_file($progressPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = json_decode((string) @file_get_contents($progressPath), true);
|
||||||
|
if (! is_array($decoded)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->gitSyncProgressPercent = (int) ($decoded['percent'] ?? $this->gitSyncProgressPercent);
|
||||||
|
$this->gitSyncProgressMessage = (string) ($decoded['message'] ?? $this->gitSyncProgressMessage);
|
||||||
|
$this->gitSyncProgressStatus = (string) ($decoded['status'] ?? $this->gitSyncProgressStatus);
|
||||||
|
|
||||||
|
if (! in_array($this->gitSyncProgressStatus, ['completed', 'failed'], true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exitCode = isset($decoded['exit_code']) && is_numeric($decoded['exit_code'])
|
||||||
|
? (int) $decoded['exit_code']
|
||||||
|
: ($this->gitSyncProgressStatus === 'completed' ? 0 : 1);
|
||||||
|
|
||||||
|
$this->lastGitSyncExitCode = $exitCode;
|
||||||
|
$this->lastGitSyncAt = now()->format('d/m/Y H:i:s');
|
||||||
|
$this->lastGitSyncOutput = is_file($logPath) ? trim((string) @file_get_contents($logPath)) : ($this->gitSyncProgressMessage ?: '');
|
||||||
|
$this->lastGitSyncIssue = $exitCode === 0 ? null : $this->detectGitSyncIssue((string) ($this->lastGitSyncOutput ?? ''));
|
||||||
|
$this->gitSyncInProgress = false;
|
||||||
|
|
||||||
|
if (is_file(storage_path('app/support/generated/git-sync-last.json'))) {
|
||||||
|
$summary = json_decode((string) @file_get_contents(storage_path('app/support/generated/git-sync-last.json')), true);
|
||||||
|
if (is_array($summary) && isset($summary['synced_at'])) {
|
||||||
|
$this->lastGitDocsSyncAt = (string) $summary['synced_at'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reloadDashboardData();
|
||||||
|
}
|
||||||
|
|
||||||
public function runMaintenanceOptimizeClear(): void
|
public function runMaintenanceOptimizeClear(): void
|
||||||
{
|
{
|
||||||
if (! $this->canRunUpdate()) {
|
if (! $this->canRunUpdate()) {
|
||||||
|
|
@ -289,12 +433,16 @@ public function getUpdatePlannedStepsProperty(): array
|
||||||
|
|
||||||
$steps = [
|
$steps = [
|
||||||
'Verifica canale update: ' . $this->updateChannel,
|
'Verifica canale update: ' . $this->updateChannel,
|
||||||
'Backup pre-update automatico (snapshot differenziale + indice record)',
|
$this->updateSkipBackup
|
||||||
$requireDrive
|
? 'Backup pre-update saltato manualmente (modalita test/debug)'
|
||||||
? 'Upload backup su Google Drive obbligatorio prima dell update'
|
: 'Backup pre-update automatico (snapshot differenziale + indice record)',
|
||||||
: ($driveEnabled
|
$this->updateSkipBackup
|
||||||
? 'Upload backup su Google Drive tentato se configurato sul sito corrente'
|
? 'Upload backup su Google Drive non eseguito perche il backup e stato saltato'
|
||||||
: 'Upload backup su Google Drive non obbligatorio per questo nodo'),
|
: ($requireDrive
|
||||||
|
? 'Upload backup su Google Drive obbligatorio prima dell update'
|
||||||
|
: ($driveEnabled
|
||||||
|
? 'Upload backup su Google Drive tentato se configurato sul sito corrente'
|
||||||
|
: 'Upload backup su Google Drive non obbligatorio per questo nodo')),
|
||||||
'Esecuzione in modalita ' . ($this->updateDryRun ? 'dry-run (nessuna scrittura)' : 'apply (aggiornamento reale)'),
|
'Esecuzione in modalita ' . ($this->updateDryRun ? 'dry-run (nessuna scrittura)' : 'apply (aggiornamento reale)'),
|
||||||
'Flag force: ' . ($this->updateForce ? 'abilitato' : 'disabilitato'),
|
'Flag force: ' . ($this->updateForce ? 'abilitato' : 'disabilitato'),
|
||||||
'Check endpoint update consigliato prima del lancio',
|
'Check endpoint update consigliato prima del lancio',
|
||||||
|
|
@ -439,13 +587,17 @@ private function startUpdateJob(bool $fallback): void
|
||||||
$this->updateJobId = $jobId;
|
$this->updateJobId = $jobId;
|
||||||
$this->updateInProgress = true;
|
$this->updateInProgress = true;
|
||||||
$this->updateProgressPercent = 1;
|
$this->updateProgressPercent = 1;
|
||||||
$this->updateProgressMessage = 'Preparazione backup pre-update...';
|
$this->updateProgressMessage = $this->updateSkipBackup
|
||||||
|
? 'Preparazione aggiornamento senza backup (modalita test)...'
|
||||||
|
: 'Preparazione backup pre-update...';
|
||||||
$this->updateProgressStatus = 'running';
|
$this->updateProgressStatus = 'running';
|
||||||
|
|
||||||
@file_put_contents($progressPath, json_encode([
|
@file_put_contents($progressPath, json_encode([
|
||||||
'timestamp' => now()->toIso8601String(),
|
'timestamp' => now()->toIso8601String(),
|
||||||
'percent' => 1,
|
'percent' => 1,
|
||||||
'message' => 'Preparazione backup pre-update...',
|
'message' => $this->updateSkipBackup
|
||||||
|
? 'Preparazione aggiornamento senza backup (modalita test)...'
|
||||||
|
: 'Preparazione backup pre-update...',
|
||||||
'status' => 'running',
|
'status' => 'running',
|
||||||
'exit_code' => null,
|
'exit_code' => null,
|
||||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
|
@ -454,6 +606,11 @@ private function startUpdateJob(bool $fallback): void
|
||||||
$requireDrive = $this->shouldRequireDrivePreupdateBackup();
|
$requireDrive = $this->shouldRequireDrivePreupdateBackup();
|
||||||
$driveEnabled = $this->shouldAttemptDrivePreupdateBackup();
|
$driveEnabled = $this->shouldAttemptDrivePreupdateBackup();
|
||||||
|
|
||||||
|
if ($this->updateSkipBackup) {
|
||||||
|
$requireDrive = false;
|
||||||
|
$driveEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
if ($requireDrive && $adminId <= 0) {
|
if ($requireDrive && $adminId <= 0) {
|
||||||
$this->updateInProgress = false;
|
$this->updateInProgress = false;
|
||||||
$this->updateProgressStatus = 'failed';
|
$this->updateProgressStatus = 'failed';
|
||||||
|
|
@ -472,58 +629,62 @@ private function startUpdateJob(bool $fallback): void
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$backupParams = [
|
if (! $this->updateSkipBackup) {
|
||||||
'--differential' => true,
|
$backupParams = [
|
||||||
'--tag' => 'update-' . $jobId,
|
'--differential' => true,
|
||||||
];
|
'--tag' => 'update-' . $jobId,
|
||||||
|
];
|
||||||
|
|
||||||
if ($driveEnabled && $adminId > 0) {
|
if ($driveEnabled && $adminId > 0) {
|
||||||
$backupParams['--drive'] = true;
|
$backupParams['--drive'] = true;
|
||||||
$backupParams['--admin-id'] = (string) $adminId;
|
$backupParams['--admin-id'] = (string) $adminId;
|
||||||
|
|
||||||
if ($requireDrive) {
|
if ($requireDrive) {
|
||||||
$backupParams['--require-drive'] = true;
|
$backupParams['--require-drive'] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$backupExit = Artisan::call('netgescon:preupdate-backup', $backupParams);
|
$backupExit = Artisan::call('netgescon:preupdate-backup', $backupParams);
|
||||||
$backupOut = trim((string) Artisan::output());
|
$backupOut = trim((string) Artisan::output());
|
||||||
if ($backupExit !== 0) {
|
if ($backupExit !== 0) {
|
||||||
|
$this->updateInProgress = false;
|
||||||
|
$this->updateProgressStatus = 'failed';
|
||||||
|
$this->updateProgressPercent = 100;
|
||||||
|
$this->updateProgressMessage = 'Backup pre-update fallito';
|
||||||
|
$this->lastUpdateExitCode = 1;
|
||||||
|
$this->lastUpdateOutput = $backupOut;
|
||||||
|
$this->lastUpdateIssue = 'Backup pre-update fallito: aggiornamento bloccato per sicurezza.';
|
||||||
|
@file_put_contents($progressPath, json_encode([
|
||||||
|
'timestamp' => now()->toIso8601String(),
|
||||||
|
'percent' => 100,
|
||||||
|
'message' => 'Backup pre-update fallito',
|
||||||
|
'status' => 'failed',
|
||||||
|
'exit_code' => 1,
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
$this->loadLatestBackupSummary();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->loadLatestBackupSummary();
|
||||||
|
} catch (Throwable $e) {
|
||||||
$this->updateInProgress = false;
|
$this->updateInProgress = false;
|
||||||
$this->updateProgressStatus = 'failed';
|
$this->updateProgressStatus = 'failed';
|
||||||
$this->updateProgressPercent = 100;
|
$this->updateProgressPercent = 100;
|
||||||
$this->updateProgressMessage = 'Backup pre-update fallito';
|
$this->updateProgressMessage = 'Eccezione backup pre-update';
|
||||||
$this->lastUpdateExitCode = 1;
|
$this->lastUpdateExitCode = 1;
|
||||||
$this->lastUpdateOutput = $backupOut;
|
$this->lastUpdateOutput = $e->getMessage();
|
||||||
$this->lastUpdateIssue = 'Backup pre-update fallito: aggiornamento bloccato per sicurezza.';
|
$this->lastUpdateIssue = 'Eccezione durante backup pre-update: aggiornamento bloccato.';
|
||||||
@file_put_contents($progressPath, json_encode([
|
@file_put_contents($progressPath, json_encode([
|
||||||
'timestamp' => now()->toIso8601String(),
|
'timestamp' => now()->toIso8601String(),
|
||||||
'percent' => 100,
|
'percent' => 100,
|
||||||
'message' => 'Backup pre-update fallito',
|
'message' => 'Eccezione backup pre-update',
|
||||||
'status' => 'failed',
|
'status' => 'failed',
|
||||||
'exit_code' => 1,
|
'exit_code' => 1,
|
||||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
$this->loadLatestBackupSummary();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->loadLatestBackupSummary();
|
} else {
|
||||||
} catch (Throwable $e) {
|
$this->appendSupportEvent('update_skip_backup', 'Backup pre-update saltato', 'Modalita test/debug attivata da Supporto > Modifiche');
|
||||||
$this->updateInProgress = false;
|
|
||||||
$this->updateProgressStatus = 'failed';
|
|
||||||
$this->updateProgressPercent = 100;
|
|
||||||
$this->updateProgressMessage = 'Eccezione backup pre-update';
|
|
||||||
$this->lastUpdateExitCode = 1;
|
|
||||||
$this->lastUpdateOutput = $e->getMessage();
|
|
||||||
$this->lastUpdateIssue = 'Eccezione durante backup pre-update: aggiornamento bloccato.';
|
|
||||||
@file_put_contents($progressPath, json_encode([
|
|
||||||
'timestamp' => now()->toIso8601String(),
|
|
||||||
'percent' => 100,
|
|
||||||
'message' => 'Eccezione backup pre-update',
|
|
||||||
'status' => 'failed',
|
|
||||||
'exit_code' => 1,
|
|
||||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$args = [
|
$args = [
|
||||||
|
|
@ -563,7 +724,9 @@ private function startUpdateJob(bool $fallback): void
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Aggiornamento avviato')
|
->title('Aggiornamento avviato')
|
||||||
->body('Backup pre-update completato. Avanzamento visibile in tempo reale.')
|
->body($this->updateSkipBackup
|
||||||
|
? 'Aggiornamento avviato senza backup pre-update (modalita test). Avanzamento visibile in tempo reale.'
|
||||||
|
: 'Backup pre-update completato. Avanzamento visibile in tempo reale.')
|
||||||
->success()
|
->success()
|
||||||
->send();
|
->send();
|
||||||
}
|
}
|
||||||
|
|
@ -731,6 +894,59 @@ private function loadRecentFilamentPages(): void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function loadGitWorkspaceStatus(): void
|
||||||
|
{
|
||||||
|
$this->gitCurrentBranch = $this->runGitProcess(['rev-parse', '--abbrev-ref', 'HEAD']);
|
||||||
|
$this->gitCurrentCommit = $this->runGitProcess(['rev-parse', '--short', 'HEAD']);
|
||||||
|
$this->gitRemoteCommit = $this->runGitProcess(['rev-parse', '--short', 'refs/remotes/' . $this->gitRemote . '/' . $this->gitBranch]);
|
||||||
|
$this->gitWorkingTreeDirty = trim((string) ($this->runGitProcess(['status', '--porcelain']) ?? '')) !== '';
|
||||||
|
|
||||||
|
$aheadBehind = $this->runGitProcess(['rev-list', '--left-right', '--count', 'HEAD...refs/remotes/' . $this->gitRemote . '/' . $this->gitBranch]);
|
||||||
|
if (is_string($aheadBehind) && preg_match('/^(\d+)\s+(\d+)$/', trim($aheadBehind), $matches) === 1) {
|
||||||
|
$this->gitAheadBehind = 'ahead ' . $matches[1] . ' / behind ' . $matches[2];
|
||||||
|
} else {
|
||||||
|
$this->gitAheadBehind = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
$summaryPath = storage_path('app/support/generated/git-sync-last.json');
|
||||||
|
if (is_file($summaryPath)) {
|
||||||
|
$summary = json_decode((string) @file_get_contents($summaryPath), true);
|
||||||
|
if (is_array($summary)) {
|
||||||
|
$this->lastGitDocsSyncAt = isset($summary['synced_at']) ? (string) $summary['synced_at'] : $this->lastGitDocsSyncAt;
|
||||||
|
$this->lastGitSyncAt = isset($summary['synced_at']) ? (string) $summary['synced_at'] : $this->lastGitSyncAt;
|
||||||
|
$this->lastGitSyncExitCode = isset($summary['exit_code']) && is_numeric($summary['exit_code'])
|
||||||
|
? (int) $summary['exit_code']
|
||||||
|
: $this->lastGitSyncExitCode;
|
||||||
|
$this->lastGitSyncIssue = isset($summary['status']) && (string) $summary['status'] === 'failed'
|
||||||
|
? (string) ($summary['message'] ?? $this->lastGitSyncIssue)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadDevelopmentSnapshot(): void
|
||||||
|
{
|
||||||
|
$generatedPath = storage_path('app/support/generated/CONTINUITA-SVILUPPO-SNAPSHOT.md');
|
||||||
|
$jsonPath = storage_path('app/support/generated/CONTINUITA-SVILUPPO-SNAPSHOT.json');
|
||||||
|
$fallbackPath = base_path('docs/support/CONTINUITA-SVILUPPO-AGENT.md');
|
||||||
|
|
||||||
|
$path = is_file($generatedPath) ? $generatedPath : $fallbackPath;
|
||||||
|
$raw = is_file($path)
|
||||||
|
? (string) @file_get_contents($path)
|
||||||
|
: '# Snapshot sviluppo non disponibile' . PHP_EOL . PHP_EOL . '- Rigenera la snapshot dalla pagina Supporto > Modifiche.';
|
||||||
|
|
||||||
|
$this->developmentSnapshotPath = $path;
|
||||||
|
$this->developmentSnapshotHtml = $this->renderSupportMarkdown($raw);
|
||||||
|
$this->developmentSnapshotGeneratedAt = null;
|
||||||
|
|
||||||
|
if (is_file($jsonPath)) {
|
||||||
|
$decoded = json_decode((string) @file_get_contents($jsonPath), true);
|
||||||
|
if (is_array($decoded) && isset($decoded['generated_at'])) {
|
||||||
|
$this->developmentSnapshotGeneratedAt = (string) $decoded['generated_at'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function loadFunctionalHighlights(): void
|
private function loadFunctionalHighlights(): void
|
||||||
{
|
{
|
||||||
$this->functionalHighlights = [];
|
$this->functionalHighlights = [];
|
||||||
|
|
@ -773,13 +989,178 @@ private function humanizePagePath(string $path): string
|
||||||
|
|
||||||
public function setActiveTab(string $tab): void
|
public function setActiveTab(string $tab): void
|
||||||
{
|
{
|
||||||
if (! in_array($tab, ['errori', 'commit', 'migliorie'], true)) {
|
if (! in_array($tab, ['manuale', 'errori', 'commit', 'migliorie'], true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->activeTab = $tab;
|
$this->activeTab = $tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function startGitSyncJob(): void
|
||||||
|
{
|
||||||
|
$jobId = now()->format('YmdHis') . '-' . Str::lower(Str::random(6));
|
||||||
|
$jobsDir = storage_path('app/support/git-sync-jobs');
|
||||||
|
if (! is_dir($jobsDir)) {
|
||||||
|
@mkdir($jobsDir, 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$progressPath = $jobsDir . '/' . $jobId . '.progress.json';
|
||||||
|
$logPath = $jobsDir . '/' . $jobId . '.log';
|
||||||
|
|
||||||
|
$this->gitSyncJobId = $jobId;
|
||||||
|
$this->gitSyncInProgress = true;
|
||||||
|
$this->gitSyncProgressPercent = 1;
|
||||||
|
$this->gitSyncProgressMessage = 'Preparazione sync Git da Gitea...';
|
||||||
|
$this->gitSyncProgressStatus = 'running';
|
||||||
|
|
||||||
|
@file_put_contents($progressPath, json_encode([
|
||||||
|
'timestamp' => now()->toIso8601String(),
|
||||||
|
'percent' => 1,
|
||||||
|
'message' => 'Preparazione sync Git da Gitea...',
|
||||||
|
'status' => 'running',
|
||||||
|
'exit_code' => null,
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
|
||||||
|
$args = [
|
||||||
|
'php',
|
||||||
|
'artisan',
|
||||||
|
'netgescon:git-sync',
|
||||||
|
'--remote=' . trim($this->gitRemote),
|
||||||
|
'--branch=' . trim($this->gitBranch),
|
||||||
|
'--progress-file=' . $progressPath,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->gitForce) {
|
||||||
|
$args[] = '--force';
|
||||||
|
}
|
||||||
|
|
||||||
|
$escaped = implode(' ', array_map(static fn(string $arg): string => escapeshellarg($arg), $args));
|
||||||
|
$shell = 'nohup ' . $escaped . ' > ' . escapeshellarg($logPath) . ' 2>&1 & echo $!';
|
||||||
|
$started = Process::path(base_path())->run(['bash', '-lc', $shell]);
|
||||||
|
|
||||||
|
if (! $started->successful()) {
|
||||||
|
$this->gitSyncInProgress = false;
|
||||||
|
$this->gitSyncProgressStatus = 'failed';
|
||||||
|
$this->gitSyncProgressPercent = 100;
|
||||||
|
$this->gitSyncProgressMessage = 'Impossibile avviare sync Git';
|
||||||
|
$this->lastGitSyncExitCode = 1;
|
||||||
|
$this->lastGitSyncOutput = trim((string) ($started->output() !== '' ? $started->output() : $started->errorOutput()));
|
||||||
|
$this->lastGitSyncIssue = 'Avvio sync Git fallito.';
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->gitSyncProgressPercent = 5;
|
||||||
|
$this->gitSyncProgressMessage = 'Job sync Git avviato (PID ' . trim($started->output()) . ')';
|
||||||
|
$this->gitSyncProgressStatus = 'running';
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('Sync Git avviata')
|
||||||
|
->body('Il nodo verra allineato da Gitea senza usare il terminale.')
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadManualSections(): void
|
||||||
|
{
|
||||||
|
$this->manualSections = [];
|
||||||
|
|
||||||
|
$documents = [
|
||||||
|
[
|
||||||
|
'key' => 'manuale-operativo',
|
||||||
|
'title' => 'Manuale operativo sviluppo',
|
||||||
|
'description' => 'Quadro principale: come stiamo sviluppando, cosa e stato implementato e come continuare senza ripartire da zero.',
|
||||||
|
'path' => 'docs/support/MANUALE-SVILUPPO-OPERATIVO.md',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'pbx-panasonic',
|
||||||
|
'title' => 'PBX Panasonic stato operativo',
|
||||||
|
'description' => 'Riepilogo strutturato del lavoro fatto su CTI, CSTA, click-to-call, stato attuale e prossimi test sul centralino Panasonic.',
|
||||||
|
'path' => 'docs/support/PBX-PANASONIC-STATO-OPERATIVO.md',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'bug-risolti',
|
||||||
|
'title' => 'Bug risolti e validazioni',
|
||||||
|
'description' => 'Registro sintetico dei problemi corretti, dello stato di verifica e dei punti ancora da testare.',
|
||||||
|
'path' => 'docs/support/BUG-RISOLTI-E-VALIDAZIONI.md',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'continuita-agent',
|
||||||
|
'title' => 'Continuita sviluppo e agent',
|
||||||
|
'description' => 'Regole pratiche per aggiornare documentazione, note commit e materiali utili al riaggancio delle prossime sessioni.',
|
||||||
|
'path' => 'docs/support/CONTINUITA-SVILUPPO-AGENT.md',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($documents as $document) {
|
||||||
|
$relativePath = (string) $document['path'];
|
||||||
|
$absolutePath = base_path($relativePath);
|
||||||
|
$exists = is_file($absolutePath);
|
||||||
|
$raw = $exists
|
||||||
|
? (string) @file_get_contents($absolutePath)
|
||||||
|
: '# Documento non trovato' . PHP_EOL . PHP_EOL . '- Path atteso: `' . $relativePath . '`';
|
||||||
|
|
||||||
|
$this->manualSections[] = [
|
||||||
|
'key' => (string) $document['key'],
|
||||||
|
'title' => (string) $document['title'],
|
||||||
|
'description' => (string) $document['description'],
|
||||||
|
'path' => $relativePath,
|
||||||
|
'exists' => $exists,
|
||||||
|
'html' => $this->renderSupportMarkdown($raw),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderSupportMarkdown(string $markdown): string
|
||||||
|
{
|
||||||
|
$replacements = [
|
||||||
|
'[E!]' => '<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-danger-500"></span><span class="font-mono">E!</span></span>',
|
||||||
|
'[U]' => '<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-success-500"></span><span class="font-mono">U</span></span>',
|
||||||
|
'[P]' => '<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-primary-500"></span><span class="font-mono">P</span></span>',
|
||||||
|
'[E]' => '<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-warning-500"></span><span class="font-mono">E</span></span>',
|
||||||
|
];
|
||||||
|
|
||||||
|
return str_replace(
|
||||||
|
array_keys($replacements),
|
||||||
|
array_values($replacements),
|
||||||
|
Str::markdown($markdown)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runGitProcess(array $arguments): ?string
|
||||||
|
{
|
||||||
|
$result = Process::path(base_path())
|
||||||
|
->timeout(120)
|
||||||
|
->run(array_merge(['git'], $arguments));
|
||||||
|
|
||||||
|
if (! $result->successful()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = trim((string) $result->output());
|
||||||
|
|
||||||
|
return $output !== '' ? $output : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function detectGitSyncIssue(string $output): string
|
||||||
|
{
|
||||||
|
$haystack = Str::lower($output);
|
||||||
|
|
||||||
|
if (str_contains($haystack, 'working tree sporco')) {
|
||||||
|
return 'Il nodo ha modifiche locali non committate: la sync Git e stata bloccata per sicurezza.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($haystack, 'not possible to fast-forward') || str_contains($haystack, 'divergent branches')) {
|
||||||
|
return 'Il branch locale non e in fast-forward rispetto a Gitea. Serve riallineamento controllato.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($haystack, 'could not read from remote repository') || str_contains($haystack, 'permission denied')) {
|
||||||
|
return 'Il nodo non riesce a leggere il repository remoto Gitea. Verificare chiavi SSH o permessi Git.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Sync Git terminata con errore tecnico. Controlla l output completo.';
|
||||||
|
}
|
||||||
|
|
||||||
public function setBugFilterStatus(string $status): void
|
public function setBugFilterStatus(string $status): void
|
||||||
{
|
{
|
||||||
if (! in_array($status, ['all', 'open', 'resolved'], true)) {
|
if (! in_array($status, ['all', 'open', 'resolved'], true)) {
|
||||||
|
|
@ -912,6 +1293,12 @@ private function detectUpdateIssue(string $output, int $exitCode): ?string
|
||||||
|
|
||||||
$haystack = Str::lower($output);
|
$haystack = Str::lower($output);
|
||||||
|
|
||||||
|
if (str_contains($haystack, 'manifest endpoint non disponibile sul server update')
|
||||||
|
|| str_contains($haystack, 'manifest endpoint non trovato')
|
||||||
|
|| str_contains($haystack, '/api/v1/distribution/updates/manifest')) {
|
||||||
|
return 'Il server distribution non espone ancora il manifest update richiesto. Per staging il flusso corretto e usare "Aggiorna staging da Gitea" nel tab Manuale; il canale distribution resta separato e non e ancora il percorso operativo principale.';
|
||||||
|
}
|
||||||
|
|
||||||
if (str_contains($haystack, 'curl error 28') || str_contains($haystack, 'operation timed out') || str_contains($haystack, 'timeout')) {
|
if (str_contains($haystack, 'curl error 28') || str_contains($haystack, 'operation timed out') || str_contains($haystack, 'timeout')) {
|
||||||
return 'Timeout di rete durante update (cURL error 28). Verifica connettivita server/repository e riprova.';
|
return 'Timeout di rete durante update (cURL error 28). Verifica connettivita server/repository e riprova.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@
|
||||||
use App\Console\Commands\ImportGesconF24LegacyCommand;
|
use App\Console\Commands\ImportGesconF24LegacyCommand;
|
||||||
use App\Console\Commands\IstatSetIndiceCommand;
|
use App\Console\Commands\IstatSetIndiceCommand;
|
||||||
use App\Console\Commands\IstatSyncIndiciCommand;
|
use App\Console\Commands\IstatSyncIndiciCommand;
|
||||||
|
use App\Console\Commands\NetgesconArchiveRegistrySyncCommand;
|
||||||
|
use App\Console\Commands\NetgesconDevelopmentSnapshotCommand;
|
||||||
use App\Console\Commands\NetgesconDistributionPullCommand;
|
use App\Console\Commands\NetgesconDistributionPullCommand;
|
||||||
|
use App\Console\Commands\NetgesconGitSyncCommand;
|
||||||
use App\Console\Commands\NetgesconPreupdateBackupCommand;
|
use App\Console\Commands\NetgesconPreupdateBackupCommand;
|
||||||
use App\Console\Commands\NetgesconQaUnitaNominativiCommand;
|
use App\Console\Commands\NetgesconQaUnitaNominativiCommand;
|
||||||
use App\Console\Commands\NetgesconRestoreRecordCommand;
|
use App\Console\Commands\NetgesconRestoreRecordCommand;
|
||||||
|
|
@ -56,7 +59,10 @@
|
||||||
GesconImportAlignCommand::class,
|
GesconImportAlignCommand::class,
|
||||||
ImportGesconF24LegacyCommand::class,
|
ImportGesconF24LegacyCommand::class,
|
||||||
NetgesconQaUnitaNominativiCommand::class,
|
NetgesconQaUnitaNominativiCommand::class,
|
||||||
|
NetgesconArchiveRegistrySyncCommand::class,
|
||||||
|
NetgesconDevelopmentSnapshotCommand::class,
|
||||||
NetgesconDistributionPullCommand::class,
|
NetgesconDistributionPullCommand::class,
|
||||||
|
NetgesconGitSyncCommand::class,
|
||||||
NetgesconPreupdateBackupCommand::class,
|
NetgesconPreupdateBackupCommand::class,
|
||||||
NetgesconRestoreRecordCommand::class,
|
NetgesconRestoreRecordCommand::class,
|
||||||
PanasonicCstaBridgeCommand::class,
|
PanasonicCstaBridgeCommand::class,
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,19 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 flex flex-wrap gap-2">
|
<div class="mt-3 flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
wire:click="setActiveTab('manuale')"
|
||||||
|
class="inline-flex items-center rounded-md px-3 py-1.5 text-xs font-medium {{ $this->activeTab === 'manuale' ? 'bg-slate-800 text-white' : 'bg-slate-100 text-slate-700 hover:bg-slate-200' }}"
|
||||||
|
>
|
||||||
|
Manuale
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
wire:click="setActiveTab('errori')"
|
wire:click="setActiveTab('errori')"
|
||||||
class="inline-flex items-center rounded-md px-3 py-1.5 text-xs font-medium {{ $this->activeTab === 'errori' ? 'bg-slate-800 text-white' : 'bg-slate-100 text-slate-700 hover:bg-slate-200' }}"
|
class="inline-flex items-center rounded-md px-3 py-1.5 text-xs font-medium {{ $this->activeTab === 'errori' ? 'bg-slate-800 text-white' : 'bg-slate-100 text-slate-700 hover:bg-slate-200' }}"
|
||||||
>
|
>
|
||||||
Errori
|
Aggiornamenti + Bug
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -53,11 +60,215 @@ class="inline-flex items-center rounded-md px-3 py-1.5 text-xs font-medium {{ $t
|
||||||
</div>
|
</div>
|
||||||
</x-filament::section>
|
</x-filament::section>
|
||||||
|
|
||||||
|
@if($this->activeTab === 'manuale')
|
||||||
|
<x-filament::section class="!p-4">
|
||||||
|
<div class="grid gap-3 lg:grid-cols-[1.35fr_0.65fr]">
|
||||||
|
<div class="rounded-lg border bg-slate-50 p-3">
|
||||||
|
<div class="text-xs font-semibold text-slate-800">Manuale operativo integrato nel progetto</div>
|
||||||
|
<div class="mt-2 text-[11px] leading-relaxed text-slate-700">
|
||||||
|
Questa sezione diventa il punto di continuita tra sviluppo, Git e aggiornamenti applicativi. I contenuti sotto arrivano da file Markdown versionati nel repository, quindi restano leggibili sia dalla UI sia durante le prossime sessioni di sviluppo con agent e strumenti locali.
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 flex flex-wrap gap-2">
|
||||||
|
<x-filament::button size="sm" color="gray" wire:click="reloadDashboardData">
|
||||||
|
Ricarica manuale e dati pagina
|
||||||
|
</x-filament::button>
|
||||||
|
<x-filament::button size="sm" color="info" wire:click="setActiveTab('commit')">
|
||||||
|
Vai a Commit + Note
|
||||||
|
</x-filament::button>
|
||||||
|
<x-filament::button size="sm" color="warning" wire:click="setActiveTab('errori')">
|
||||||
|
Vai a Aggiornamenti + Bug
|
||||||
|
</x-filament::button>
|
||||||
|
<x-filament::button size="sm" wire:click="runUpdate" :disabled="!$this->canRunUpdate()">
|
||||||
|
Lancia aggiornamento
|
||||||
|
</x-filament::button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-lg border bg-white p-3">
|
||||||
|
<div class="text-xs font-semibold text-slate-800">Flusso minimo dopo ogni Git</div>
|
||||||
|
<ol class="mt-2 list-decimal space-y-1 pl-4 text-[11px] text-slate-700">
|
||||||
|
<li>aggiorna il documento della sezione corretta nel repository</li>
|
||||||
|
<li>salva una nota operativa sul commit se serve contesto extra</li>
|
||||||
|
<li>spingi il codice su Gitea</li>
|
||||||
|
<li>usa il pulsante di sync da Gitea da questa pagina per allineare staging</li>
|
||||||
|
<li>se c e un problema applicativo, registralo o segnalo come risolto nel tab BUG</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-filament::section>
|
||||||
|
|
||||||
|
<x-filament::section class="!p-4">
|
||||||
|
<div class="mb-2 flex flex-wrap items-start justify-between gap-2">
|
||||||
|
<div>
|
||||||
|
<div class="text-xs font-semibold text-gray-700">Snapshot continuita sviluppo</div>
|
||||||
|
<div class="mt-1 text-[11px] text-gray-500">
|
||||||
|
Riepilogo automatico per ripartire senza ricostruire ogni volta ragionamenti, file chiave, stato PBX e punti ancora aperti.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<div class="rounded bg-slate-100 px-2 py-1 font-mono text-[10px] text-slate-600">{{ $this->developmentSnapshotPath ?? '-' }}</div>
|
||||||
|
<x-filament::button size="sm" color="gray" wire:click="generateDevelopmentSnapshot" :disabled="!$this->canRunUpdate()">
|
||||||
|
Rigenera snapshot sviluppo
|
||||||
|
</x-filament::button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 rounded-lg border bg-gray-50 p-3 text-[11px] text-gray-700">
|
||||||
|
Generata il: <span class="font-medium">{{ $this->developmentSnapshotGeneratedAt ?? '-' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="prose prose-sm max-w-none text-[11px] leading-relaxed">
|
||||||
|
{!! $this->developmentSnapshotHtml !!}
|
||||||
|
</div>
|
||||||
|
</x-filament::section>
|
||||||
|
|
||||||
|
<x-filament::section class="!p-4">
|
||||||
|
<div class="grid gap-3 lg:grid-cols-[1.15fr_0.85fr]">
|
||||||
|
<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 staging da Gitea</div>
|
||||||
|
<div class="mb-3 text-[11px] text-gray-600">
|
||||||
|
Questo e il flusso principale da usare sui nodi interni: pull da Gitea, rigenerazione changelog Git per la pagina supporto e pulizia cache applicativa. Non richiede terminale.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2 sm:grid-cols-4">
|
||||||
|
<label class="block">
|
||||||
|
<span class="mb-1 block text-[11px] font-medium">Remote</span>
|
||||||
|
<input type="text" wire:model="gitRemote" class="w-full rounded-md border-gray-300 text-xs" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="mb-1 block text-[11px] font-medium">Branch</span>
|
||||||
|
<input type="text" wire:model="gitBranch" class="w-full rounded-md border-gray-300 text-xs" />
|
||||||
|
</label>
|
||||||
|
<label class="inline-flex items-center gap-2 rounded-md border bg-white px-2 py-1.5 text-[11px] sm:mt-6">
|
||||||
|
<input type="checkbox" wire:model="gitForce" class="rounded border-gray-300" />
|
||||||
|
Force con working tree sporco
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 flex flex-wrap gap-2">
|
||||||
|
<x-filament::button size="sm" wire:click="runGitSync" :disabled="!$this->canRunUpdate()">
|
||||||
|
Aggiorna staging da Gitea
|
||||||
|
</x-filament::button>
|
||||||
|
<x-filament::button size="sm" color="info" wire:click="refreshGitSyncProgress" :disabled="!$this->gitSyncInProgress">
|
||||||
|
Aggiorna avanzamento Git
|
||||||
|
</x-filament::button>
|
||||||
|
<x-filament::button size="sm" color="gray" wire:click="reloadDashboardData">
|
||||||
|
Rileggi stato Git
|
||||||
|
</x-filament::button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($this->gitSyncInProgress)
|
||||||
|
<div class="mt-3 rounded-lg border bg-white p-3" wire:poll.2s="refreshGitSyncProgress">
|
||||||
|
@else
|
||||||
|
<div class="mt-3 rounded-lg border bg-white p-3">
|
||||||
|
@endif
|
||||||
|
<div class="mb-1 flex items-center justify-between text-[11px] text-gray-600">
|
||||||
|
<span class="font-semibold">Avanzamento sync Git</span>
|
||||||
|
<span class="font-mono">{{ (int) $this->gitSyncProgressPercent }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-2 w-full overflow-hidden rounded bg-gray-200">
|
||||||
|
<div class="h-2 {{ $this->gitSyncProgressStatus === 'failed' ? 'bg-rose-500' : 'bg-emerald-500' }}" style="width: {{ max(0, min(100, (int) $this->gitSyncProgressPercent)) }}%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-[11px] text-gray-600">{{ $this->gitSyncProgressMessage }}</div>
|
||||||
|
<div class="mt-1 text-[10px] text-gray-500">
|
||||||
|
Job: <span class="font-mono">{{ $this->gitSyncJobId ?? '-' }}</span>
|
||||||
|
· Stato: <span class="font-semibold">{{ $this->gitSyncProgressStatus }}</span>
|
||||||
|
@if($this->gitSyncInProgress)
|
||||||
|
· in esecuzione
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-lg border bg-white p-3">
|
||||||
|
<div class="text-xs font-semibold text-gray-700">Stato repository nodo</div>
|
||||||
|
<div class="mt-2 space-y-1 text-[11px] text-gray-600">
|
||||||
|
<div>Branch locale: <span class="font-mono font-medium">{{ $this->gitCurrentBranch ?? '-' }}</span></div>
|
||||||
|
<div>Commit locale: <span class="font-mono font-medium">{{ $this->gitCurrentCommit ?? '-' }}</span></div>
|
||||||
|
<div>Commit remoto: <span class="font-mono font-medium">{{ $this->gitRemoteCommit ?? '-' }}</span></div>
|
||||||
|
<div>Stato tracking: <span class="font-medium">{{ $this->gitAheadBehind }}</span></div>
|
||||||
|
<div>
|
||||||
|
Working tree:
|
||||||
|
<span class="font-medium {{ $this->gitWorkingTreeDirty ? 'text-rose-700' : 'text-emerald-700' }}">{{ $this->gitWorkingTreeDirty ? 'sporco' : 'pulito' }}</span>
|
||||||
|
</div>
|
||||||
|
<div>Ultima sync Git: <span class="font-medium">{{ $this->lastGitSyncAt ?? '-' }}</span></div>
|
||||||
|
<div>Rigenerazione changelog locale: <span class="font-medium">{{ $this->lastGitDocsSyncAt ?? '-' }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-lg border bg-white p-3">
|
||||||
|
<div class="text-xs font-semibold text-gray-700">Esito ultima sync Git</div>
|
||||||
|
<div class="mt-2 space-y-1 text-[11px] text-gray-600">
|
||||||
|
<div>
|
||||||
|
Exit code:
|
||||||
|
@if($this->lastGitSyncExitCode === null)
|
||||||
|
<span class="font-medium">-</span>
|
||||||
|
@elseif($this->lastGitSyncExitCode === 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->lastGitSyncExitCode }} ERRORE</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if(filled($this->lastGitSyncIssue))
|
||||||
|
<div class="mt-3 rounded border border-amber-300 bg-amber-50 px-2 py-2 text-[11px] text-amber-900">
|
||||||
|
{{ $this->lastGitSyncIssue }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(filled($this->lastGitSyncOutput))
|
||||||
|
<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 sync Git</div>
|
||||||
|
<pre class="max-h-64 overflow-auto whitespace-pre-wrap text-[11px] leading-relaxed text-slate-100">{{ $this->lastGitSyncOutput }}</pre>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</x-filament::section>
|
||||||
|
|
||||||
|
@foreach($this->manualSections as $section)
|
||||||
|
<x-filament::section class="!p-4">
|
||||||
|
<div class="mb-2 flex flex-wrap items-start justify-between gap-2">
|
||||||
|
<div>
|
||||||
|
<div class="text-xs font-semibold text-gray-700">{{ $section['title'] }}</div>
|
||||||
|
<div class="mt-1 text-[11px] text-gray-500">{{ $section['description'] }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded bg-slate-100 px-2 py-1 font-mono text-[10px] text-slate-600">{{ $section['path'] }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(! $section['exists'])
|
||||||
|
<div class="mb-3 rounded border border-amber-300 bg-amber-50 px-3 py-2 text-[11px] text-amber-900">
|
||||||
|
Documento non ancora presente nel repository. La sezione e predisposta per essere popolata progressivamente.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="prose prose-sm max-w-none text-[11px] leading-relaxed">
|
||||||
|
{!! $section['html'] !!}
|
||||||
|
</div>
|
||||||
|
</x-filament::section>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
|
||||||
@if($this->activeTab === 'errori')
|
@if($this->activeTab === 'errori')
|
||||||
<x-filament::section class="!p-4">
|
<x-filament::section class="!p-4">
|
||||||
|
<div class="mb-3 rounded-lg border border-amber-300 bg-amber-50 p-3 text-[11px] text-amber-900">
|
||||||
|
<div class="font-semibold">Uso corretto di questa sezione</div>
|
||||||
|
<div class="mt-1">
|
||||||
|
Per il nodo staging, il percorso operativo principale e la sync Git da Gitea disponibile nel tab <span class="font-semibold">Manuale</span>. I pulsanti sotto servono al canale <span class="font-semibold">distribution</span>, che al momento non e ancora il flusso update attivo principale e puo restituire errore manifest 404.
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<x-filament::button size="sm" color="warning" wire:click="setActiveTab('manuale')">
|
||||||
|
Vai a Manuale e sync Gitea
|
||||||
|
</x-filament::button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-3 lg:grid-cols-3">
|
<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="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="mb-2 text-xs font-semibold text-gray-700">Aggiornamento da server distribution</div>
|
||||||
|
<div class="mb-3 text-[11px] text-gray-500">
|
||||||
|
Usare questi pulsanti solo quando il server <span class="font-mono">updates.netgescon.it</span> espone correttamente il manifest update. Per staging standard usare il flusso Git/Gitea nel tab Manuale.
|
||||||
|
</div>
|
||||||
<div class="grid gap-2 sm:grid-cols-3">
|
<div class="grid gap-2 sm:grid-cols-3">
|
||||||
<label class="block">
|
<label class="block">
|
||||||
<span class="mb-1 block text-[11px] font-medium">Canale</span>
|
<span class="mb-1 block text-[11px] font-medium">Canale</span>
|
||||||
|
|
@ -76,6 +287,13 @@ class="inline-flex items-center rounded-md px-3 py-1.5 text-xs font-medium {{ $t
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<label class="inline-flex items-center gap-2 rounded-md border border-amber-300 bg-amber-50 px-2 py-1.5 text-[11px] text-amber-900">
|
||||||
|
<input type="checkbox" wire:model="updateSkipBackup" class="rounded border-gray-300" />
|
||||||
|
Salta backup pre-update solo per test/debug su staging
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 flex flex-wrap gap-2">
|
<div class="mt-3 flex flex-wrap gap-2">
|
||||||
<x-filament::button size="sm" wire:click="runUpdate" :disabled="!$this->canRunUpdate()">
|
<x-filament::button size="sm" wire:click="runUpdate" :disabled="!$this->canRunUpdate()">
|
||||||
Lancia aggiornamento
|
Lancia aggiornamento
|
||||||
|
|
@ -411,22 +629,12 @@ class="inline-flex items-center rounded-md px-3 py-1.5 text-xs font-medium {{ $t
|
||||||
$path = base_path('docs/NETGESCON-MODIFICHE.md');
|
$path = base_path('docs/NETGESCON-MODIFICHE.md');
|
||||||
$raw = file_exists($path) ? file_get_contents($path) : "# Modifiche\n\nFile non trovato: {$path}\n";
|
$raw = file_exists($path) ? file_get_contents($path) : "# Modifiche\n\nFile non trovato: {$path}\n";
|
||||||
|
|
||||||
$gitPath = base_path('docs/NETGESCON-GIT-AUTOCHANGELOG.md');
|
$gitGeneratedPath = storage_path('app/support/generated/NETGESCON-GIT-AUTOCHANGELOG.md');
|
||||||
|
$gitPath = file_exists($gitGeneratedPath) ? $gitGeneratedPath : base_path('docs/NETGESCON-GIT-AUTOCHANGELOG.md');
|
||||||
$gitRaw = file_exists($gitPath) ? file_get_contents($gitPath) : "# Git Auto Changelog\n\nFile non trovato: {$gitPath}\nEsegui: `bash scripts/ops/netgescon-sync-git-changelog.sh`\n";
|
$gitRaw = file_exists($gitPath) ? file_get_contents($gitPath) : "# Git Auto Changelog\n\nFile non trovato: {$gitPath}\nEsegui: `bash scripts/ops/netgescon-sync-git-changelog.sh`\n";
|
||||||
|
|
||||||
// Mini-render: sostituisce tag tipo [U], [P], [E], [E!] con un quadratino colorato.
|
$rawHtml = $this->renderSupportMarkdown($raw);
|
||||||
$replacements = [
|
$gitRawHtml = $this->renderSupportMarkdown($gitRaw);
|
||||||
'[E!]' => '<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-danger-500"></span><span class="font-mono">E!</span></span>',
|
|
||||||
'[U]' => '<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-success-500"></span><span class="font-mono">U</span></span>',
|
|
||||||
'[P]' => '<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-primary-500"></span><span class="font-mono">P</span></span>',
|
|
||||||
'[E]' => '<span class="inline-flex items-center gap-2"><span class="inline-block h-3 w-3 rounded-sm bg-warning-500"></span><span class="font-mono">E</span></span>',
|
|
||||||
];
|
|
||||||
|
|
||||||
$rawHtml = \Illuminate\Support\Str::markdown($raw);
|
|
||||||
$rawHtml = str_replace(array_keys($replacements), array_values($replacements), $rawHtml);
|
|
||||||
|
|
||||||
$gitRawHtml = \Illuminate\Support\Str::markdown($gitRaw);
|
|
||||||
$gitRawHtml = str_replace(array_keys($replacements), array_values($replacements), $gitRawHtml);
|
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@if($this->activeTab === 'migliorie')
|
@if($this->activeTab === 'migliorie')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user